Download multiple plots using 1 single download button in dash

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