plotly / plotly.R

An interactive graphing library for R
https://plotly-r.com
Other
2.57k stars 627 forks source link

Save Locally Generated Plot Locally As PNG/JPG #311

Closed robertleitner closed 8 years ago

robertleitner commented 9 years ago

How can I save an interactive plot generated by plotly locally as png? The commands png()...dev.off() won't capture the plot command and the function call plotly_IMAGE throws an authorization error although the plot is generated locally and I'm logged in.

Thanks for your help.

cpsievert commented 9 years ago

Hmm, plotly_IMAGE() should work...does plotly_POST() work for you? If not, you probably haven't set your credentials properly. You can use plotly:::verify("username") and plotly:::verify("api_key") to double-check the R package can find them.

You can also open the interactive viz in a web browser and use plotly's "download as plot png" button (this isn't yet working in RStudio, but we're working on it)

screen shot 2015-11-25 at 9 32 06 pm

rodrigomurta commented 8 years ago

Here is a SO related post: exporting png files from plotly in r without internet connection The idea is to create it without UI interaction and with no internet connection. plotly_POST() works, but need internet. I believe that with the new Open JS Strategy that this functionality make sense. tks

cpsievert commented 8 years ago

You could use something like RSelenium to automate this, but I won't be adding any native support to plotly's R package.

xzmagic commented 8 years ago

does plotly_IMAGE only work with username and api_key?

rodrigomurta commented 8 years ago

@xzmagic unfortunately yes. The conversion is created in Plotly server, not locally.

cpsievert commented 8 years ago

@timelyportfolio bootstrapping off of discusion in https://github.com/plotly/plotly.js/issues/83, what do you think about using https://github.com/jeroenooms/rsvg to produce bitmaps locally?

cpsievert commented 8 years ago

(of course I'd prefer avoiding an extra R dependency if a pure JS solution seems plausible)

timelyportfolio commented 8 years ago

Funny, I had the same idea this morning :) I started yesterday on very preliminary work to attach the toImage method to a plot. I think we could tie this in. However, until the nested svgs are eliminated, RStudio will still render them incorrectly. Perhaps I could demonstrate a method in a Gist of how we could do what you are saying with rsvg. Would that work?

Remember though also the newest knitr + webshot will handle a lot of these cases automatically.

cpsievert commented 8 years ago

That'd be great!

timelyportfolio commented 8 years ago

Here are two ways of accomplishing a static image of a Plotly graph. If using in rmarkdown, I highly recommend using the newest knitr to automatically convert htmlwidgets (see knitr News).

# In issue 311
#   there was some discussion how we might
#   get our plotly graph as a static image.
#   Below are two ways to accomplish this:
#    1.  Use webshot or do automatically with knitr
#          If you want more info about the auto-knitr
#          method, then please see
#          https://github.com/yihui/knitr/blame/c3085229f5a02506e40f21c1cecf6d8448f5caaa/NEWS.md#L7-L10
#
#    2.  Use Shiny gadgets and rsvg
#

Method #2 has the advantage of multiple export formats.

Webshot

######## Webshot ##################################
library(plotly)
#  devtools::install_github("wch/webshot")
library(webshot)
library(htmltools)
library(magrittr)

#  make a simple plotly graph to illustrate
ggp <- ggplotly(
  ggplot(cars, aes(speed,dist)) + geom_point(),
  height=400,
  width=400
)

png_webshot <- tempfile(fileext = ".png")

tagList(
  htmlwidgets:::as.tags.htmlwidget(as.widget(
    ggp
  ))
) %>%
  html_print %>%
  # get forward slash on windows
  normalizePath(.,winslash="/") %>%
  # replace drive:/ with drive:// so C:/ becomes C://
  gsub(x=.,pattern = ":/",replacement="://") %>%
  # appends file:/// to make valid uri
  paste0("file:///",.) %>%
  # screenshot it for lots of good reasons
  webshot( file = png_webshot, delay = 1 )

#  see our new png in your image viewer of choice
system(sprintf("open %s", shQuote(png_webshot)))

file2e28719021f9

Shiny gadgets and rsvg

######## Shiny gadgets and rsvg ###################

library(plotly)
library(rsvg)
library(shiny)
library(miniUI)
library(htmlwidgets)
library(htmltools)

# make a little Shiny miniUI gadget
#  borrowed some code from chemdoodle
#  https://github.com/zachcp/chemdoodle/blob/master/R/chemdoodle_sketcher_gadgets.R
plotly_gadget <- function(plotly_widget, height=NULL, width=NULL){

  ui <- miniPage(
    miniContentPanel(
      htmlwidgets:::as.tags.htmlwidget(as.widget(plotly_widget))
    ),

    gadgetTitleBar("Get Static Image...", right = miniTitleBarButton("done", "Done", primary = TRUE)),

    tags$script('
      document.getElementById("done").onclick = function() {
        var plotly_svg = Plotly.Snapshot.toSVG(
          document.querySelectorAll(".plotly")[0]
        );
        Shiny.onInputChange("plotly_svg", plotly_svg);
      };
    ')
  )

  server <- function(input, output, session) {
    observeEvent(input$done, { stopApp(input$plotly_svg) })
  }

  runGadget(ui,
            server,
            viewer =  dialogViewer("PlotlyStaticImage",
                                   width = width,
                                   height = height))
}

#  make a simple plotly graph to illustrate
ggp <- ggplotly(
  ggplot(cars, aes(speed,dist)) + geom_point(),
  height=400,
  width=400
)

#  run our gadget to get the svg
svg <- plotly_gadget(ggp)

#  use rsvg to convert our svg to png
#    feel free to do something other than png
png_gadget <- tempfile(fileext=".png")
rsvg_png(charToRaw(svg), png_gadget)

#  see our new png in your image viewer of choice
system(sprintf("open %s", shQuote(png_gadget)))

file2e2813587d40

cpsievert commented 8 years ago

Thanks @timelyportfolio. I think for now we'll just leave this be and hope plotly.js provides a solution. For the record, I think I prefer the webshot approach, but I don't want to depend on it.

jackparmer commented 8 years ago

Quick PSA 🎤 plotly.js now has an officially supported API for offline image export: https://plot.ly/javascript/static-image-export/ 🎉

@yankev is is adding programmatic offline export support to the Python client in this PR: https://github.com/plotly/plotly.py/pull/494

@spencerlyon2 has added incredible offline image export for the Julia client described here: http://spencerlyon.com/PlotlyJS.jl/manipulating_plots/#saving-figures

timelyportfolio commented 8 years ago

I will try to add this in over next couple of days.

cpsievert commented 8 years ago

@timelyportfolio can we get away with export without a full-blown DOM renderer?

I've added this function to the major update I've been working on locally (I'll start the pull request tomorrow probably). What do you think?

#' Export a plotly graph to a static file
#' 
#' @param p a plotly or ggplot object.
#' @param file a filename. See the file argument of \code{webshot::webshot} 
#' for valid extensions.
#' @param ... arguments passed onto \code{webshot::webshot}
#' @export
#' @examples \dontrun{
#' export(plot_ly(economics, x = ~date, y = ~pce))
#' }
export <- function(p, file = "plotly.png", ...) {
  if (system.file(package = "webshot") == "") {
    stop(
      'Please install the webshot package ',
      '(if not on CRAN, try devtools::install_github("wch/webshot"))'
    )
  }
  f <- basename(tempfile('plotly', '.', '.html'))
  on.exit(unlink(f), add = TRUE)
  html <- htmlwidgets::saveWidget(plotly_build(p), f)
  webshot::webshot(f, file, ...)
}
timelyportfolio commented 8 years ago

I don't see any way possible to export without a full-blown DOM renderer.

Excited to see your changes.

cpsievert commented 8 years ago

Closed via d2bf2a1eb56d824a091e2cc9afea09a5968dd050.