5

I have a plotly graph (in my case made in R / rmarkdown), and would like to download the data.

  • Option 1 is a download button outside the graph to a data object (e.g CSV, or a table). For example Download output file csv, or this. It works, but not that neat - using custom modebar would be neater.

Options 2 and 3 use a custom modebar button. plotly book - custom modebar button has a nice example with code to make the custom button.

  • Option 2, is a download button (e.g. to csv) as a plotly custom modebar button. Presumably this could be done in javascript (or html) and passed through, similar to example below in option 3? Suggestions for how to do this? There is some discussion on this option at Plotly forum, using Dash(?), but that is well over my pay grade.

  • Option 3 is a button that links to a table (possibly an appendix or other website with a datatable object), where that datatable has a download button. The last part, making a button for datatable that you can click is easy (see here). The javascript code for a website link should look something like this (javascript link to website). The default Plotly button in a plotly modebar does send you to a linked website when you click, but not sure how to replicate that html/javascript from R.

Am I missing something simple in this code?

Edit The icon web link below works, once opened in a browser - i.e. clicking on the icon wont open a website from the Rstudio viewer.

#devtools::install_github('cpsievert/plotly_book') #for octocat image
library(tidyverse)
library(DT)
library(plotly)

data(octocat_svg_path, package = "plotlyBook")

octocat <- list(
    name = "octocat",
    icon = list(
      path = octocat_svg_path,
      transform = 'matrix(1 0 0 1 -2 -2) scale(0.7)'
    ),
    click = htmlwidgets::JS(
      "function(gd) {window.location.href = 'http://www.google.com';
    }"
    )
  )
plot_ly() %>%
    config(modeBarButtonsToAdd = list(octocat))
Mark Neal
  • 996
  • 16
  • 52
  • What is plotlyBook? Is it this? https://github.com/cpsievert/plotly_book. Is "octocat_svg_path" the name of a dataset in plotlyBook? Otherwise what is octocat_svg_path? This is going to be in an Rmarkdown file deployed on a webpage? Maybe a reprex would help. – Simon Woodward Nov 21 '19 at 21:54
  • Oh ok yes it is this package. – Simon Woodward Nov 21 '19 at 22:11
  • 1
    Sorry, have edited code to include devtools command to download package where image resides, as per example. – Mark Neal Nov 22 '19 at 00:31
  • The first option is quite nice since you can structure the data as a nice csv. – Simon Woodward Nov 24 '19 at 21:34
  • 1
    I think the third option might be the best compromise: Link to download (via a table available elsewhere or embedded file) is inside the graph area, but full control over what is downloaded, as opposed to downloading traces, which appears to give more difficult to consume tables of data when graphs are more complex. – Mark Neal Dec 04 '19 at 00:13

1 Answers1

12

Using onRender from htmlwidgets you can add JS event handlers. This one will print the clicked data series to a window.

https://plotly-r.com/js-event-handlers.html

Here is an example in an Rmd file. Open this in RStudio and click [Knit to HTML] then [Open in Browser]. When you click one of the data series, a new windows will open containing the data in csv format.

---
title: "Export Plotly Data"
output: html_document
---

```{r echo = FALSE, message = FALSE}
library(plotly)
library(htmlwidgets)

plot_ly() %>%
    add_markers(x = c(0, 1), y = c(2, 3)) %>%
    add_markers(x = c(4, 5), y = c(6, 7)) %>%
    onRender("
    function(el) {
      el.on('plotly_click', function(d) {
        var newWindow = window.open();
        newWindow.document.write('x,', d.points[0].data.x, '<br>y,', d.points[0].data.y);
      });
    }
  ")

Update 1

Here is an example with ggplotly

https://community.plot.ly/t/returning-specific-data-element-with-plotly-click-function/5670

---
title: "Export Plotly Data"
output: html_document
---

```{r echo = FALSE, message = FALSE}
library(plotly)
library(htmlwidgets)
library(ggplot2)

data <- data.frame(
    Time = round(runif(10), 2), 
    Value = round(runif(10), 2),
    Type = rep(c("A", "B"), each = 5)
    )

gg <- ggplot(data = data) +
    geom_point(mapping = aes(x = Time, y = Value, colour = Type), size = 2)

ggplotly(gg) %>%
    onRender("
    function(el) {
      el.on('plotly_click', function(d) {
        var newWindow = window.open();
        newWindow.document.write(
          d.points[0].xaxis.title.text, ',',
          d.points[0].data.x, '<br>',
          d.points[0].data.name, ',',
          d.points[0].data.y
          );
      });
    }
  ")

Update 2

Here's an example using a custom modebar, as you originally had, that prints all data series. Icon svg data obtained from http://svgicons.sparkk.fr/

---
title: "Export Plotly Data"
output: html_document
---

```{r echo = FALSE, message = FALSE}
library(plotly)
library(htmlwidgets)
library(ggplot2)

data <- data.frame(
    Time = round(runif(10), 2),
    Value = round(runif(10), 2),
    Type = rep(c("A", "B"), each = 5)
    )

gg <- ggplot(data = data) +
    theme(legend.title = element_blank()) +
    geom_point(mapping = aes(x = Time, y = Value, colour = Type), size = 2)

# http://svgicons.sparkk.fr/
icon_svg_path = "M19.404,6.65l-5.998-5.996c-0.292-0.292-0.765-0.292-1.056,0l-2.22,2.22l-8.311,8.313l-0.003,0.001v0.003l-0.161,0.161c-0.114,0.112-0.187,0.258-0.21,0.417l-1.059,7.051c-0.035,0.233,0.044,0.47,0.21,0.639c0.143,0.14,0.333,0.219,0.528,0.219c0.038,0,0.073-0.003,0.111-0.009l7.054-1.055c0.158-0.025,0.306-0.098,0.417-0.211l8.478-8.476l2.22-2.22C19.695,7.414,19.695,6.941,19.404,6.65z M8.341,16.656l-0.989-0.99l7.258-7.258l0.989,0.99L8.341,16.656z M2.332,15.919l0.411-2.748l4.143,4.143l-2.748,0.41L2.332,15.919z M13.554,7.351L6.296,14.61l-0.849-0.848l7.259-7.258l0.423,0.424L13.554,7.351zM10.658,4.457l0.992,0.99l-7.259,7.258L3.4,11.715L10.658,4.457z M16.656,8.342l-1.517-1.517V6.823h-0.003l-0.951-0.951l-2.471-2.471l1.164-1.164l4.942,4.94L16.656,8.342z"

dl_button <- list(
    name = "Download data",
    icon = list(
        path = icon_svg_path,
        transform = "scale(0.84) translate(-1, 0)"
        ),
    click = htmlwidgets::JS("
          function(gd) {
            var html = '';
            for(var i = 0; i < gd.data.length; i++){
              html += gd.layout.xaxis.title.text + ' ' + gd.data[i].name + ',' + gd.data[i].x + '<br>';
              html += gd.layout.yaxis.title.text + ' ' + gd.data[i].name + ',' + gd.data[i].y + '<br>';
            }
            var newWindow = window.open();
            newWindow.document.write(html);
          }
   ")
)

ggplotly(gg) %>%
    layout(legend = list(y = 0.5)) %>%
    config(modeBarButtonsToAdd = list(dl_button))

Update 3

This version opens the Save As dialogue to write the data to a file.

Download .txt using JavaScript without dialog prompt

---
title: "Export Plotly Data"
output: html_document
---

```{r echo = FALSE, message = FALSE}
library(plotly)
library(htmlwidgets)
library(ggplot2)

data <- data.frame(
    Time = round(runif(10), 2),
    Value = round(runif(10), 2),
    Type = rep(c("A", "B"), each = 5)
    )

gg <- ggplot(data = data) +
    theme(legend.title = element_blank()) +
    geom_point(mapping = aes(x = Time, y = Value, colour = Type), size = 2)

# http://svgicons.sparkk.fr/
icon_svg_path = "M15.608,6.262h-2.338v0.935h2.338c0.516,0,0.934,0.418,0.934,0.935v8.879c0,0.517-0.418,0.935-0.934,0.935H4.392c-0.516,0-0.935-0.418-0.935-0.935V8.131c0-0.516,0.419-0.935,0.935-0.935h2.336V6.262H4.392c-1.032,0-1.869,0.837-1.869,1.869v8.879c0,1.031,0.837,1.869,1.869,1.869h11.216c1.031,0,1.869-0.838,1.869-1.869V8.131C17.478,7.099,16.64,6.262,15.608,6.262z M9.513,11.973c0.017,0.082,0.047,0.162,0.109,0.226c0.104,0.106,0.243,0.143,0.378,0.126c0.135,0.017,0.274-0.02,0.377-0.126c0.064-0.065,0.097-0.147,0.115-0.231l1.708-1.751c0.178-0.183,0.178-0.479,0-0.662c-0.178-0.182-0.467-0.182-0.645,0l-1.101,1.129V1.588c0-0.258-0.204-0.467-0.456-0.467c-0.252,0-0.456,0.209-0.456,0.467v9.094L8.443,9.553c-0.178-0.182-0.467-0.182-0.645,0c-0.178,0.184-0.178,0.479,0,0.662L9.513,11.973z"

dl_button <- list(
    name = "Download data",
    icon = list(
        path = icon_svg_path,
        transform = "scale(0.84) translate(-1, -1)"
        ),
    click = htmlwidgets::JS("
          function(gd) {
            var text = '';
            for(var i = 0; i < gd.data.length; i++){
              text += gd.layout.xaxis.title.text + gd.data[i].name + ',' + gd.data[i].x + '\\n';
              text += gd.layout.yaxis.title.text + gd.data[i].name + ',' + gd.data[i].y + '\\n';
            };
            var blob = new Blob([text], {type: 'text/plain'});
            var a = document.createElement('a');
            const object_URL = URL.createObjectURL(blob);
            a.href = object_URL;
            a.download = 'data.csv';
            document.body.appendChild(a);
            a.click();
            URL.revokeObjectURL(object_URL);
          }
   ")
)

ggplotly(gg) %>%
    layout(legend = list(y = 0.5)) %>%
    config(modeBarButtonsToAdd = list(dl_button))

Community
  • 1
  • 1
Simon Woodward
  • 1,946
  • 1
  • 16
  • 24
  • 1
    Brilliant implementation, extra +1 for adding the nice download image for the button! – Mark Neal Nov 26 '19 at 01:56
  • I'm not sure how it will handle more more complex plots. But this structure can be extended if necessary I think. – Simon Woodward Nov 26 '19 at 02:16
  • 1
    There may be some difficulties with specific types of axes, including dates, and lining them up with the respective data points - I'm investigating. – Mark Neal Nov 26 '19 at 02:48
  • For the more complex datasets, I think my default solution will be to make the download link point to a **datatable** table with download buttons, like [this](https://rstudio.github.io/DT/003-tabletools-buttons.html) – Mark Neal Mar 10 '20 at 04:40
  • I ended up using **gt** instead of **DT** for the table, so for the download I used this [solution](https://cran.r-project.org/web/packages/downloadthis/vignettes/downloadthis.html), which utilises the package **downloadthis**, which can also be applied straight below a graph. – Mark Neal Oct 01 '20 at 23:06
  • 1
    this bit of code has been amazing for all kinds of plotlys. I have managed to modify the loop for a number of different chart types. I was wondering how can I access annotation text from plots in the download? – zimia Sep 21 '21 at 15:44
  • 1
    to answer my own question its `gd.layout.annotations[j].text` where j is the number of subplots you have (as my annotations are basically subplot titles). the entire js structure can be accessed via `plotly_json(p)` where p is the plot. – zimia Sep 22 '21 at 07:28