4

I would like to use ggplotly for it's side effect the same way ggplot does or even graphics does. By this I mean when I knitr::knit or rmarkdown::render a Rmd document I expect print(obj) where obj is a ggplotly objcet to be in the report and that's not the case.

  • Can anyone explain what is going on?
  • Can anyone tell me how I can achieve want I want to do. I want to be able to plot a ggplotly graph into a function without returning the object (I want to return the underlying data of the graph) and I'd like the code to work for both ggplot and ggplotly (i.e. use the same code for a ggplot or ggplotly)

question.R file

#+ libs, echo = FALSE                                                                                                                                                                                                                                        
suppressMessages({                                                                                                                                                                                                                             
    library(ggplot2)                                                                                                                                                                                                                           
    library(plotly)                                                                                                                                                                                                                            
    library(rmarkdown)                                                                                                                                                                                                                         
})                                                                                                                                                                                                                                             

#+ functions decl, echo = FALSE
df <- data.frame(x = 1:5, y = 1:5)                                                                                                                                                                                                                             
f_0 <- function(df) {                                                                                                                                                                                                                                                                                                                                                                                                                                     
    p <- ggplot(df, aes(x, y)) + geom_line()
    # p or plot(p) or print(p) works                                                                                                                                                                                                   
    print(p)                                                                                                                                                                                                                                   
    return(df)                                                                                                                                                                                                                                 
}                                                                                                                                                                                                                                              
f_1 <- function(df) {                                                                                                                                                                                                                                                                                                                                                                                                                                     
    p <- ggplot(df, aes(x, y)) + geom_line()                                                                                                                                                                                                   
    p <- ggplotly(p)    
    # plot(p) crashes                                                                                                                                                                                                                       
    # print p does not print in report                                                                                                                                                                                                         
    print(p)                                                                                                                                                                                                                                   
    # p standalone does not work either                                                                                                                                                                                                        
    p                                                                                                                                                                                                                                          
    return(df)                                                                                                                                                                                                                                 
}                                                                                                                                                                                                                                              

#' # plots                                                                                                                                                                                                                                     
#' plot 0                                                                                                                                                                                                                                      
#+ plot_0                                                                                                                                                                                                                                      
res_0 <- f_0(df)                                                                                                                                                                                                                                 
#' plot 1                                                                                                                                                                                                                                      
#+ plot_1                                                                                                                                                                                                                                      
res_1 <- f_1(df)     

Render this file

rmarkdown::render("question.R")

The output

output

M--
  • 25,431
  • 8
  • 61
  • 93
statquant
  • 13,672
  • 21
  • 91
  • 162

1 Answers1

3

Editorial comment: As a point of style, it's usually a good idea to separate computation and plotting into distinct functions, because it increases modularity, makes code easier to maintain, and allows for finer control without parameter creep. The output of individual functions can then be easily mapped to separate knitr chunks.

Best solution: I know that you are specifically asking about not returning the plot object, but I just want to point out that returning it alongside the results provides the cleanest and most elegant solution:

---
output: html_document
---

```{r include=FALSE}
library( tidyverse )
df <- data_frame( x=1:5, y=1:5 )
```

```{r}
f <- function(df) {
  gg <- ggplot(df, aes(x,y)) + geom_point()
  list( result=df, plot=plotly::ggplotly(gg) )
}
res <- f(df)
res$plot
```

However, if you absolutely cannot return a plotly object from a function, you have some alternatives.

Option 1: Store the plotly object to the parent frame, providing access to it from the knitr chunk.

```{r}
f1 <- function(df) {
  gg <- ggplot(df, aes(x,y)) + geom_point()
  assign("ggp", plotly::ggplotly(gg), envir=parent.frame())
  df    # NOT returning a plot
}
res1 <- f1(df)
ggp   # Let knitr handle the rendering naturally
```

Option 2: Render the plot to a temporary .html and then import it as an iframe

```{r, results='asis'}     # <-- note the "asis" chunk option
f2 <- function(df)
{
  gg <- ggplot(df, aes(x,y)) + geom_point()
  htmlwidgets::saveWidget( plotly::ggplotly(gg), "temp.html")
  print( htmltools::tags$iframe(src="temp.html", width=640, height=480) )
  df    # NOT returning a plot
}
res2 <- f2(df)
```

Explanation: Yihui can correct me if I'm wrong, but knitr basically does "Option 2" behind the scenes. It renders htmlwidgets, such as plotly objects, into temporary .html files and then combines those temporaries to produce the final document. The reason it fails inside a function is because the temporary file gets deleted when execution leaves the function scope. (You can replicate this yourself by using tempfile() instead of the persistent "temp.html"; the iframe object will display a "file not found" error in the final document.) There's probably a way to modify knitr hooks to retain the temporary files, but it's beyond my knowledge. Lastly, the reason you don't have this issue with basic ggplot objects is because their output goes to the plotting device, which persists across calling frames.

Artem Sokolov
  • 13,196
  • 4
  • 43
  • 74
  • as I said in the question I want to plot within a function without returning the plot object – statquant Dec 02 '19 at 21:37
  • @statquant: What is the benefit of producing plots inline with `print()` instead of returning the plot object alongside the results? I expanded the post to show some alternatives, but doing `return(list( results, plot ))` is a much cleaner option, imho. – Artem Sokolov Dec 03 '19 at 17:22
  • Hello, I see your point but I find it a waste given that the plot object already contains the data, for ggplot you can get plot$data, but for ggplotly the API is different I don't know why. Anyway thanks for your answer I'll close this – statquant Dec 04 '19 at 07:36