Download raw data

@chriddyp still facing the same error, Downloaded excel gives some Html script instead of data frame.
I am using python 3.6 and dash ‘0.28.5’

csv download works like a charm, thank you! Does anyone have the code snippets to download a pandas DataFrame in dash as json or Excel File for the client to save?

I have an Excel file dynamically generated based on a variable called “option”, so in my layout I have a button like this (in this example option=foo but these links are dynamically generated):

html.A(html.Button('Export to Excel'), href=f'/export/excel?option=foo')

And I create a flask route like this:

@app.server.route('/export/excel')
def export_excel_file():
    option = flask.request.args.get('option')
    if any(l not in string.ascii_letters for l in option):
         raise ValueError('Option contains unexpected characters')
    option_df = function_that_generates_dataframe(option)

    xlsx_io = io.BytesIO()
    writer = pd.ExcelWriter(xlsx_io, engine='xlsxwriter')
    option_df.to_excel(writer, sheet_name=option)
    writer.save()
    xlsx_io.seek(0)

    return flask.send_file(
        xlsx_io,
        mimetype='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
        attachment_filename=f'export_{option}.xlsx',
        as_attachment=True,
        cache_timeout=0
    )

This works perfectly for me, you may be able to simplify it if you don’t need to pass in a variable.

1 Like

Thanks Damian !

What is more, you need to create a callback that pass the variables to the flask, for me that was not immediately obvious… here is a simple example:

@app.callback(
    Output('download_xlsx', 'href'),
    [Input('field-dropdown', 'value')])
def update_download_link(filter_value):
    return f'/export/excel?option={filter_value}'

Here you find a complete simple example sticking everything together.

2 Likes

Is it also possible to pass on data that I have just created in the application? So that I no longer use a global pandas DataFrame but pass on data with json or bytes format to the download component?

Can I make use of the html.A paramters?

html.A(
        id='download-link',
        download="",
        href="",
        target=""
    )

I understand for csv it is as easy as:

@app.callback(
    Output('download-link', 'href'),
    [
    Input('pass_tables_json', 'children'),
    Input('page_index', 'value'),
    Input('select_table', 'value')
    ])
def update_download_link(json_dfs_tbl, page, table):
    if json_dfs_tbl:
        logger.info(f'App.Callback: update_download_link(): { page} , table: {table}')
        json_dfs_tbl = json.loads(json_dfs_tbl)
        page = page - 1
        col = json_dfs_tbl['extracted_tbl']['index']
        npt = np.array(json_dfs_tbl['extracted_tbl']['data'])
        test = pd.DataFrame(npt, index=np.array(col)).loc[f'df{page}_{table}']
       
        csv_string = test.to_csv(index=False, header = None, encoding='utf-8')
        csv_string = "data:text/csv;charset=utf-8," + urllib.parse.quote(csv_string)
        return csv_string

But how would I achieve the same in excel download?

Great Question! I also want to make something work like this

1 Like

csv_string = “data:text/csv;charset=utf-8,” + urllib.parse.quote(csv_string)

instead of
csv_string = “data:text/csv;charset=utf-8,” + urllib.quote(csv_string)

worked for me

The example you posted doesn’t work. I get the same syntax as @pray

Here is a cleaned up working example

import dash
import dash_core_components as dcc
import dash_html_components as html
import pandas as pd
import urllib
#from urllib.parse import urlparse
#import urllib.parse
#urllib.parse.quote
#from urllib.quote import quote

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,%EF%BB%BF" + urllib.parse.quote(csv_string)
    return csv_string


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

Something I have noticed is that when I use the csv download method, I keep getting the previous dataframe that was created when I pass in the dataframe in the method instead of having a method (filter_data) create the dataframe locally. Is there a way to get the current dataframe that’s being used in the program? I store my data in Store, which is not typically a problem to get because I don’t get this issue with other parts of my program

@app.callback(Output('download-link-2', 'href'),
               [Input(component_id='download-link-2',component_property='n_clicks')],
               [State(component_id='data-stored-2',component_property='data')])
def download_data_2(clicks,data):
    data = pd.DataFrame(data,columns=df.columns)
    csv_string = data.to_csv(index=False, encoding='utf-8')
    csv_string = "data:text/csv;charset=utf-8,%EF%BB%BF" + parse.quote(csv_string)
    return csv_string
                
host = socket.gethostbyname(socket.gethostname())
app.run_server(debug=False, host=host, port = 1001)
1 Like

Has anyone found a solution to dayxx369’s problem? I have encountered the same issue.

1 Like

The solution I described here should work:

It saves data based on what is contained within the dcc.Graph.figure.data object, but can easily be modified to handle the contents of dcc.Store.data or dt.DataTable.data.

edit doing so for a server-side thing won’t be possible. I suppose using the simple example from dayxx369, you would pass in State('table', 'children') and need to parse that to regenerate the CSV.

Hi Chris, is it also possible to give a path/location where the data should be exported to?

3 Likes

@chriddyp how can we choose what the filename is? instead of download=‘rawdata’ can we set this via an input box? I just don’t know what to return in Output(‘id’,’?’)

Is adding a file path browser possible when you click a download button?

Hi Chris,
I have the same problem as what Pray is facing. Going through all those postings, it still does not help.
My code as below (I am using Python 3):

dcc.Tab(label=‘Weather’, children=[
html.Div([
html.H4(string_weather),
html.Iframe(id=‘map1’, srcDoc = open(’/home/tanthiamhuat/DataAnalyticsPortal/data/SingaporeWeather.html’,‘r’).read(), width=‘100%’,height=‘530’),
html.A(
‘Download Data’,
id=‘download-link’,
download=“Weather.csv”,
href="",
target="_blank"
)
]),
]),

@app.callback(
Output(‘download-link’, ‘href’),
[Input(‘cust-dropdown’, ‘value’)]
)
def update_download_link():
Temperature_Rainfall = pd.read_csv(’/home/tanthiamhuat/DataAnalyticsPortal/data/Location_DeviceTemperatureRainfall.csv’)
csv_string = Temperature_Rainfall.to_csv(index=False, encoding=‘utf-8’)
csv_string = “data:text/csv;charset=utf-8,%EF%BB%BF” + urllib.parse.quote(csv_string)
return csv_string

Take note here, I have no Input for that Download link, and I am using some other which is not related to that Download link. If that Download link is not related to any input, how should we put it?

Thanks very helpful!

I’ve a slightly different use case: Instead of providing a download link for a csv file I provide a download link for a plain text file with link = "data:text/plain;charset=utf-8," + urllib.parse.quote(text). However the text is formatted and I’m getting the text in the file unformatted. I’ve not found another suitable media type which could work for me. Has someone an idea how to fix this?

Hey - I’m getting a syntax error in this part. Can you please advise?

f’/export/excel?option={filter_value}’

Hey!
@chriddyp

I tried using this code and run it without any changes. The file downloaded is not in the correct format. Can you help me resolve this