Download raw data

is there a simple way to make the charts raw data downloadable client-side?
I do not want to host downloadable files on server, instead something like a csv file creation directly client-side from the pandas dataframe…
I’ve seen it can be done with javascript but I’m not sure about the integration with Dash

5 Likes

I do not want to host downloadable files on server

One option is to host the files remotely but download them on-the-fly (in a callback) or on app start. Here’s an example:

df = pd.read_csv('https://raw.githubusercontent.com/plotly/datasets/master/2011_february_aa_flight_paths.csv')

I’ve seen it can be done with javascript

The other option would be to write your own Dash component that would download the file client side. We have a tutorial on writing Dash plugins here: Build Your Own Components | Dash for Python Documentation | Plotly and a tutorial on React here: https://dash.plot.ly/react-for-python-developers. Companies can also sponsor open source Dash components.

got it, but as an intermediate workaround, what about a function provided in an external JS file? I am not sure to understand how Dash deals with the JS scripts externally linked, for example if I append in the app ( via app.scripts.append_script ) this simple save.js script https://gist.github.com/hamxiaoz/a664f52e34c22f2be83f can it be used directly on a pd.to_csv() output? if yes, how exactly is it invoked?

You could use app.scripts.append_script to run a custom script as well. It will run as soon as the script is loaded, after the rest of the scripts (the built-in dash-renderer scripts and all of the scripts for the component libraries) are loaded but perhaps before the app’s content itself is rendered.

ok I’ll give a try to this also then, thanks!

In shiny there is downloadLink (GUI) and downloadHandler (SERVER / callback)
I hope we will get a similar solution with Dash

GUI
downloadLink("download_summary", h3("Download Summary"))
SERVER (callback)

  output$download_summary <- downloadHandler(
  
  filename = function() { "summary.xlsx" },
  
  content = function(file){
    wb <- loadWorkbook(file, create = TRUE)
    
    ### Data
    d = [dataframe and manipulation...]
    
    createSheet(wb, name = "Data")
    writeWorksheet(wb, d, sheet = "Data")

    saveWorkbook(wb)
  }
)

I suppose we could be considering flask-excel in the meantime?

a workaround I found for my case is to create the data on the fly while downloading client-side
here for example I want to download a grouped dataframe where the grouping field is chosen by a dropdown menu (selcol in the update function):

@app.callback(
    dash.dependencies.Output('download-chosen', 'href'),
    [dash.dependencies.Input('field-dropdown-download', 'value')])
def update_downloader(selcol):
    groupedf = dfdate.groupby([selcol])['anothercol'].agg(lambda x:len(x.unique())).reset_index()    
    csvString = groupedf[[selcol,'anothercol']].to_csv(index=False,encoding='utf-8')    
    csvString = "data:text/csv;charset=utf-8," + urllib.quote(csvString)
    return csvString

where what gets updated is a clickable download link

html.Div([
        html.Label('Choose category for grouping data:'),
        dcc.Dropdown(
            id='field-dropdown-download',
            options=[
                    {'label': 'antenna name', 'value': 'AntennaName'},
                    {'label': 'utsett sted', 'value': 'Utsett.sted'},
                    {'label': 'oppdrett', 'value': 'date'},
                    {'label': 'gruppe', 'value': 'Gruppe'}
            ],
            value='AntennaName'
        ),
    ],style={'font-family': myfont,'width': '20%'}),
    html.Div([
        html.A(children='' , id='download-chosen',download="rawdata-groupedbychosenfield.csv", href="",target="_blank"),        ]} 
    ),
12 Likes

Wow, @carlottanegri that’s brilliant! :bowing_man:

Here’s a full example of this approach that readers can copy and paste:

import dash
import dash_core_components as dcc
import dash_html_components as html
import pandas as pd
import urllib

df = pd.DataFrame({
    'a': [1, 2, 3, 4],
    'b': [2, 1, 5, 6],
    'c': ['x', 'x', 'y', 'y']
})


def generate_table(dataframe, max_rows=10):
    return html.Table(
        # Header
        [html.Tr([html.Th(col) for col in dataframe.columns])] +

        # Body
        [html.Tr([
            html.Td(dataframe.iloc[i][col]) for col in dataframe.columns
        ]) for i in range(min(len(dataframe), max_rows))]
    )


app = dash.Dash(__name__)
app.css.append_css({"external_url": "https://codepen.io/chriddyp/pen/bWLwgP.css"})
app.layout = html.Div([
    html.Label('Filter'),

    dcc.Dropdown(
        id='field-dropdown',
        options=[
            {'label': i, 'value': i} for i in
            (['all'] + list(df['c'].unique()))],
        value='all'
    ),
    html.Div(id='table'),
    html.A(
        'Download Data',
        id='download-link',
        download="rawdata.csv",
        href="",
        target="_blank"
    )
])


def filter_data(value):
    if value == 'all':
        return df
    else:
        return df[df['c'] == value]


@app.callback(
    dash.dependencies.Output('table', 'children'),
    [dash.dependencies.Input('field-dropdown', 'value')])
def update_table(filter_value):
    dff = filter_data(filter_value)
    return generate_table(dff)


@app.callback(
    dash.dependencies.Output('download-link', 'href'),
    [dash.dependencies.Input('field-dropdown', 'value')])
def update_download_link(filter_value):
    dff = filter_data(filter_value)
    csv_string = dff.to_csv(index=False, encoding='utf-8')
    csv_string = "data:text/csv;charset=utf-8," + urllib.quote(csv_string)
    return csv_string


if __name__ == '__main__':
    app.run_server(debug=True)

26 Likes

much more useful to others than my too-specialized example :stuck_out_tongue: thanks!

1 Like

Hi Chris,

I was trying to implement something similar and I came across your great example. I tried this, it works perfectly, but the downloaded file is not what I had intended it to be. I’m running it on windows.

is this happening because of incorrect auth client id ?

Regards,
Prabhakar

4 Likes

@pray - For some reason, it looks like the link is returning the HTML of the Dash page instead. Could you copy and paste the example that you are using here? Did the example that I posted above work for you?

2 Likes

@carlottanegri That is awesome :smiley:

1 Like

I’m having the same issue as @pray – I’ll double check if I’m doing something wrong

EDIT:
I forgot to mention that @chriddyp’s example works fine, although I can’t figure what’s the difference with what I’m doing on my dash.

Thanks chriddyp for your reply. I’m using the same example as it is. I tried with my implementation first, which was giving me similar html doc, then I tried with the above example and it gave me the same html doc. With my implementation I was not able to see the table also on the web page, but with yours I was able to see the table on the webpage but the download link was giving me html doc.

I just figured out that this is because of urllib package. I was running this with python 3.4, for which I needed to import urlilb.parse instead of urllib. I’m sorry for the trouble. I’m trying to see what’s wrong with my implementation.

3 Likes

Hi Sherm4nLC

I think python version might be the culprit here. Check your python version. You will have to import urllib.parse instead or urllib for python 3.x .

8 Likes

I was having some issues downloading files over 10k rows, and I figured that with Firefox the link works better, has anyone experienced something like that?

1 Like

I didn’t do any specific test on sizes, but anyway the method is not suitable for very large downloads…

1 Like

That’s cool. This can support csv file up to 1328KB. But when I tried to increase the data size a bit it just return a ‘download error’ once the link is clicked.

interesting, would be nice to test size limits depending on… what? browser? hardware? other also?

Here’s a SO question about it: html - Data protocol URL size limitations - Stack Overflow

Quote:

Data URI Limits
The data URI spec does not define a size limit but says applications may impose their own.

  • Chrome - 2MB
  • Firefox - unlimited
  • IE ≥ 9 & Edge - 4GB
  • Safari & Mobile Safari - ?
4 Likes