plotly.js icon indicating copy to clipboard operation
plotly.js copied to clipboard

Download data button

Open dfernan opened this issue 6 years ago • 24 comments

Hi,

Would it be possible to add a download data button? Right now the default's option is a png image of the plot but I think it'd be very nice if one could also download the data inside the graph. Thus having a download data in graph button would be really cool.

It'd be a new button in here: https://github.com/plotly/plotly.js/blob/2ba7bdf30d605794de42d560cab41ebbc8681d29/src/components/modebar/buttons.js

I am not familiar with JS so it's hard for me to make a pull request on this but I do think it'd be a great option.

dfernan avatar Nov 19 '17 19:11 dfernan

@jackparmer @chriddyp @alexcjohnson what do you think, is this within the scope of plotly.js?

etpinard avatar Nov 20 '17 14:11 etpinard

I'm tempted to say we could add this if/when we create separate data and trace definitions, but the way plotly.js works at present this would be tricky and a bit ambiguous.

Also note that plotly cloud does this already - I realize that this is a few extra steps and not possible in all cases, but I bring it up because a) it may be useful in some cases, and b) knowing what went into its system for splitting the data out makes me more hesitant to build this into plotly.js.

alexcjohnson avatar Nov 20 '17 16:11 alexcjohnson

As a first step, we could add an optional button that dumps the JSON representation of the plot to a file. This would be easy to implement since there's already a "save and edit this plot in the cloud" button (which many of us can't use).

amellnik avatar Nov 20 '17 18:11 amellnik

+1 @amellnik

What do you think of having the JSON show as indented plaintext within the plot view, that you could copy/paste, instead of downloading to file (or maybe buttons at the top of this plaintext view to download JSON or CSV)? I'd love to be able to quickly inspect the JSON in a JS or embedded graph without being required to download a file.

jackparmer avatar Nov 20 '17 19:11 jackparmer

I'm tempted to say we could add this if/when we create separate data and trace definitions, but the way plotly.js works at present this would be tricky and a bit ambiguous.

I think even with missing data or obscure src tags this would still be useful for quickly inspecting how the plot was constructed.

jackparmer avatar Nov 20 '17 19:11 jackparmer

Is this really necessary?

Typing something like

var gd = document.getElementById('graph')
gd.data // => [{ /* ... */ }]
gd.layout // => { /* */ }

in the console should be easy enough.

etpinard avatar Nov 20 '17 19:11 etpinard

I'm thinking more about non-JS users here.

jackparmer avatar Nov 20 '17 19:11 jackparmer

@jackparmer I would agree with that or even just to copy the serialized JSON to the clipboard. I also think this should be end-user friendly (and not require using any js).

amellnik avatar Nov 20 '17 20:11 amellnik

Oh sorry, my https://github.com/plotly/plotly.js/issues/2171#issuecomment-345796287 should have said: Is this really necessary in plotly.js proper?

etpinard avatar Nov 28 '17 15:11 etpinard

Hi, any updates on this issue? It'd really make my life easier since right now I add a download button at the end of all my dashboards but still not the most ideal/user_friendly...

dfernan avatar Dec 28 '17 22:12 dfernan

@dfernan no updates.

We're still debating (albeit not very intensively during the holidays :wink: ) whether this request has its place in the library.

etpinard avatar Dec 29 '17 01:12 etpinard

yes of course, makes sense, please enjoy the holidays :-)

dfernan avatar Dec 29 '17 17:12 dfernan

Please do u.u

Cerebrock avatar Aug 28 '18 20:08 Cerebrock

Hi guys, is there any update on if this is planned to be done or not? would be a big help. thank you!

janpickard avatar Apr 02 '19 13:04 janpickard

In the meanwhile, here is a function I use that provides this feature. I hope it's helpful:

modeBarButtonsToAdd: [{ name: 'downloadCsv', title: 'Download data as csv', icon: Plotly.Icons.disk, click: function(){ var csvData = []; var header = ['X'] //main header. for (var j = 0; j < numSeries; j++){ header.push(y_names[j]); } csvData.push(header); var header = [x_title] //subheader for (var j = 0; j < numSeries; j++){ header.push(y_titles[j]); } csvData.push(header); for (var i = 0; i < data.length; i++){ //data var line = [data[i][x_name]]; for (var j = 0; j < numSeries; j++){ line.push(data[i][y_names[j]]); } csvData.push(line); } var csvFile = csvData.map(e=>e.map(a=>'"'+((a||"").toString().replace(/"/gi,'""'))+'"').join(",")).join("\r\n"); //quote all fields, escape quotes by doubling them. var blob = new Blob([csvFile], { type: 'text/csv;charset=utf-8;' }); var link = document.createElement("a"); var url = URL.createObjectURL(blob); link.setAttribute("href", url); link.setAttribute("download", chart_title.replace(/[^a-z0-9_.-]/gi,'_') + ".csv"); link.style.visibility = 'hidden'; document.body.appendChild(link); link.click(); document.body.removeChild(link); } }],

RichardNeill avatar Apr 13 '19 19:04 RichardNeill

@RichardNeill I'd love to use your code. I've copied it in, but am getting an undefined on numSeries. Is that what you call your rows?

yemling avatar May 03 '19 09:05 yemling

yes, that's right. I've just quoted the code as I used it. Essentially, csvData[] needs to be a 2D array of arrays, generated from your data as you use it, with a header row if you want one. In my example, I have one X axis and numSeries different y-axes, each named y_names[j].

The way to do this is:

  1. initialise csvData to empty array.
  2. create the header, as an array containing the column names.
  3. push the header array onto csvData[]
  4. foreach line in your data:
  5. create the line, as an array of data values.
  6. and push the line into csvData[]

there is also an error in the above: a||"" will coalesce all falsey types (including zero) to the empty string. We actually only want to convert NULL to the empty string, leaving zero as zero. This is somewhat down to your preference: whether false,null,true should be converted to "false","null","true", or to 0,"",1 .

HTH

RichardNeill avatar May 03 '19 22:05 RichardNeill

For those that are interested, I've successfully added a data download button in R plotly/ggplotly.

https://stackoverflow.com/questions/58924824/download-data-from-plotly-graph-via-custom-modebar-button-coded-in-r/58986421#58986421

woodwards avatar Nov 24 '19 22:11 woodwards

Is there a way to adapt this to Python?

ZooBear avatar Aug 01 '20 06:08 ZooBear

This issue has been tagged with NEEDS SPON$OR

A community PR for this feature would certainly be welcome, but our experience is deeper features like this are difficult to complete without the Plotly maintainers leading the effort.

Sponsorship range: $5k-$10k

What Sponsorship includes:

  • Completion of this feature to the Sponsor's satisfaction, in a manner coherent with the rest of the Plotly.js library and API
  • Tests for this feature
  • Long-term support (continued support of this feature in the latest version of Plotly.js)
  • Documentation at plotly.com/javascript
  • Possibility of integrating this feature with Plotly Graphing Libraries (Python, R, F#, Julia, MATLAB, etc)
  • Possibility of integrating this feature with Dash
  • Feature announcement on community.plotly.com with shout out to Sponsor (or can remain anonymous)
  • Gratification of advancing the world's most downloaded, interactive scientific graphing libraries (>50M downloads across supported languages)

Please include the link to this issue when contacting us to discuss.

jackparmer avatar Sep 10 '20 19:09 jackparmer

Here is a function that provides this feature. It works for 1-x 1-y scatter

modeBarButtonsToAdd: [
  {
    name: "downloadCsv",
    title: "Download data as csv",
    icon: Plotly.Icons.disk,
    click: (gd) => {
      let data = [
        [gd.layout.xaxis.title.text, gd.layout.yaxis.title.text].join(
          ","
        ),
      ];
      gd.data[0].x.forEach((xvalue, i) =>
        data.push([xvalue, gd.data[0].y[i]].join(","))
      );
      let blob = new Blob([data.join("\r\n")], { type: "text/csv" });
      // import { saveAs } from "file-saver";
      saveAs(
        blob,
        "export.csv"
      );
    },
  },
],

alessandroblaco avatar Apr 20 '22 10:04 alessandroblaco

This will work, but there are a few possible gotchas if you are unlucky with quoted data, data that contains a quote itself, or data that is null. Once I've got the CSV data (and header) into a 2D array, I use:

var csvFile = csvData.map(e=>e.map(a=>'"'+((a??"").toString().replace(/"/gi,'""'))+'"').join(",")).join("\r\n");  

You need to decide how you want to represent the four awkward types ("", null, true, false) in your CSV, should they occur. My choice represents them as ("","","true","false") respectively. But you might prefer to represent null as "NULL", or as an empty field (i.e. two adjacent commas.) I don't think there is a good, unambiguous way that lets you handle CSV data that potentially contains both nulls and empty strings and lets you distinguish them. But at least we can do something other than crash.

If you want null to be "NULL" rather than "", then change (a??"") to (a??"NULL")

HTH :-)

RichardNeill avatar Apr 21 '22 11:04 RichardNeill

@alessandroblaco Is there a way to export a specific trace that is not hidden instead of the first trace?

joshuakoh1 avatar Apr 22 '22 15:04 joshuakoh1

@alessandroblaco thank you! That example is definitely a fine addition to the javascript plotly docs.

Are you familiar with pappa? It is a fast csv writer. I'm wondering if it could be used in your example. The only trouble I see is the data is not simply and Array of Arrays, but more complex than that. Have you tried anything like pappa instead?: https://www.papaparse.com/docs

evbo avatar Jun 04 '22 16:06 evbo