Download multiple plots using 1 single download button in dash

I am working on a dashboard where I have created multiple plots and I want use a single download button to download all the plots. I am kind of new to dash so any help will be appreciated.

Here is the code snippet for the callback func:

@app.callback(
Output(‘daily_vehicle_cadence’, ‘figure’),
Output(“loading-output-2”, “children”),
Input(‘cadence_line_selection’, ‘value’),
Input(‘cadence_day_type_selection’, ‘value’),
)
def plot_daily_vehicle_cadence(selected_line, selected_day):
if selected_line is None or selected_day is None:
return dash.no_update
fig = plot_functions.daily_vehicle_cadence(city=city, selected_line=selected_line, selected_day=selected_day)
return fig, None

@app.callback(
Output(‘daily_loading_time’, ‘figure’),
Input(‘loading_time_day_type_selection’, ‘value’),
)
def plot_daily_loading_time(selected_day):
if selected_day is None:
return dash.no_update
fig = plot_functions.daily_loading_time(city=city, selected_day=selected_day)
return fig

@app.callback(
    Output('daily_worst_case_temp', 'figure'),
    # Input('Temp_selection', 'value'),
    Input('Temp_day_type_selection', 'value'),
)
def plot_daily_worst_temperature(selected_day):
    if selected_day is None:
        return dash.no_update
    fig = plot_functions.worst_case_charging(city=city, selected_day=selected_day)
    return fig

@app.callback(
Output(‘power’, ‘figure’),
Output(‘display_temperature’, ‘children’),
Output(‘display_temperature’,‘value’),
Input(‘power_selection’, ‘value’),
Input(‘display_temperature’, ‘n_clicks’),
State(‘display_temperature’, ‘children’),
State(‘display_temperature’, ‘value’)
)
def plot_daily_power(power_selection, change_temperature_display, temperature_text,button_state):
# if and ONLY if temperature button has been pushed changed display text to opposite
if change_temperature_display is not None and (change_temperature_display != int(button_state)):
button_state = str(change_temperature_display)
if temperature_text == ‘Hide Temperature Plot’:
temperature_text = ‘Show Temperature Plot’
elif temperature_text == ‘Show Temperature Plot’:
temperature_text = ‘Hide Temperature Plot’
# set the enable flag based on user selection
if temperature_text == ‘Hide Temperature Plot’:
# user wants to see temperature plot since button hasn’t been pushed to hide the plot
enable_temperature = True
elif temperature_text == ‘Show Temperature Plot’:
# user wants to hide temperature plot since button hasn’t been pushed to show the plot
enable_temperature = False

    if power_selection is None:
        return dash.no_update
    else:
        fig = plot_functions.power_and_average_temperature(city=city, selected_power_type=power_selection,
                                                           enable_temperature=enable_temperature)
    return fig, temperature_text, button_state

and here is the callback func trying to download them using a single button ( I dont know what to put in return) :

@app.callback(
Output(“download-component”, “data”),
Input(‘download_all’, ‘n_clicks’),
State(‘daily_vehicle_cadence’, ‘figure’),
State(‘daily_loading_time’, ‘figure’),
State(‘daily_worst_case_temp’, ‘figure’),
State(‘power’, ‘figure’),
prevent_initial_call=True,
)
def plot_nnc(n_clicks, figs1, figs2, figs3, figs4):
if n_clicks is None:
raise dash.exceptions.PreventUpdate
else:
return figs1, figs2, figs3, figs4

Please can anyone help me with this.

The error that I am getting is :
Failed component prop type: Invalid component prop data key data supplied to Download.
Bad object: {
“data”: ,
“layout”: {
“shapes”: [
{
“fillcolor”: “RoyalBlue”,
“type”: “rect”,
“x0”: 18360,
“x1”: 20160,
“y0”: 0,
“y1”: 30
},
{
“fillcolor”: “RoyalBlue”,
“type”: “rect”,
“x0”: 20160,
“x1”: 21960,
“y0”: 0,
“y1”: 30
}, and so on…

Hi,

Welcome to the community! :slight_smile:

Please make sure to format your post next time, so the code is more readable. Here is a great guideline, which includes an explanation about preformatted text for code.

The problem is that you are trying to pass a list of fig directly to the data of dcc.Download, and this is not possible. You can find the specs for the “data” dict and some examples in the documentation below:

Your question asks about downloading multiple plots with one button, and that is in my opinion slightly more advanced than a single file download. Therefore I am providing you a simple example of how to do it below:

from dash import Dash, callback, Input, Output, State, dcc, html, ALL
import plotly.express as px
import plotly.graph_objects as go

import io
from base64 import standard_b64decode, b64decode, b64encode

def fig_to_data(fig: go.Figure, filename: str="plot.html") -> dict:
    buffer = io.StringIO()
    fig.write_html(buffer)
    html_bytes = buffer.getvalue().encode()
    content = b64encode(html_bytes).decode()
    return {
        "base64": True,
        "content": content,
        "type": "text/html",
        "filename": filename
    }


app = Dash(__name__)

app.layout = html.Div([
    dcc.Graph(id={"type": 'graph', "index": 0}, figure=px.scatter(px.data.iris(), x='sepal_width', y='sepal_length', color='species')),
    dcc.Graph(id={"type": 'graph', "index": 1}, figure=px.scatter(px.data.iris(), x='petal_length', y='petal_width', color='species')),
    html.Button('Download All', id='download-button'),
    *[
        dcc.Download(id={"type": 'download', "index": i}) for i in range(2)
    ]
])

@callback(
    Output({"type": "download", "index": ALL}, "data"),
    [
        Input("download-button", "n_clicks"),
        State({"type": "graph", "index": ALL}, "figure")
    ],
    prevent_initial_call=True
)
def download_figure(n_clicks, figs):
    return [
        fig_to_data(go.Figure(fig), filename=f"plot_{idx}.html") for idx, fig in enumerate(figs)
    ]


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

Some observations about the example:

  • I used the pattern matching callback to simplify a bit, but the idea is quite general: you need N dcc.Download components if you intend to download N figures from dcc.Graph. There might be ways to do it with a simple component. but I don’t see it being simpler than this approach.
  • fig_to_data exports a html figure, which is interactive just like in the application itself (of course, without the dynamic aspects of re-writing figures with callbacks). You can use other formats and there are examples around.
  • The example above is heavily inspired by [this part of the documentation]

Hope this helps! :slight_smile:

1 Like

I love your solution, but I have been trying to find a way to make the downloaded graphs to be in picture format and not html format.