plotly / plotly.js

Open-source JavaScript charting library behind Plotly and Dash
https://plotly.com/javascript/
MIT License
16.88k stars 1.85k forks source link

Download data button #2171

Open dfernan opened 6 years ago

dfernan commented 6 years ago

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.

etpinard commented 6 years ago

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

alexcjohnson commented 6 years ago

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.

amellnik commented 6 years ago

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).

jackparmer commented 6 years ago

+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 commented 6 years ago

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.

etpinard commented 6 years ago

Is this really necessary?

Typing something like

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

in the console should be easy enough.

jackparmer commented 6 years ago

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

amellnik commented 6 years ago

@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).

etpinard commented 6 years ago

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

dfernan commented 6 years ago

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...

etpinard commented 6 years ago

@dfernan no updates.

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

dfernan commented 6 years ago

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

Cerebrock commented 6 years ago

Please do u.u

janpickard commented 5 years ago

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

RichardNeill commented 5 years ago

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", charttitle.replace(/[^a-z0-9.-]/gi,'_') + ".csv"); link.style.visibility = 'hidden'; document.body.appendChild(link); link.click(); document.body.removeChild(link); } }],

yemling commented 5 years ago

@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?

RichardNeill commented 5 years ago

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

woodwards commented 4 years ago

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

ZooBear commented 4 years ago

Is there a way to adapt this to Python?

jackparmer commented 4 years ago

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:

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

alessandroblaco commented 2 years ago

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"
      );
    },
  },
],
RichardNeill commented 2 years ago

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 :-)

joshuakoh1 commented 2 years ago

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

evbo commented 2 years ago

@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