Issue with dcc.Download

Hi,

can someone please help me understand why I am not the histogram is not being downloaded. The tab changes to Updating but nothing happens.

import dash
from dash import Input, Output, State, dcc, html
from dash.exceptions import PreventUpdate
import plotly.graph_objects as go
import plotly.express as px
import json


# Dash app
app = dash.Dash(__name__)

# App layout
app.layout = html.Div([
    html.Button('See Graph',id='graph-button'),
    dcc.Graph(id='histogram'),
    html.Button('Download', id='download-button'),
    dcc.Store(id='json_graph'),
    dcc.Download(id='download_graph')
])

@app.callback(
    Output('histogram', 'figure'),
    Output('json_graph', 'data'),
    Input('graph-button', 'n_clicks'),
    prevent_initial_call=True,
)

def show_graph(n_clicks):
    if not n_clicks:
        raise PreventUpdate
    
    print('Graph button clicked')
    if n_clicks:
        print('Graph button clicked')
        df = px.data.tips()
        fig = px.histogram(df, x="total_bill")

        return fig, fig.to_json()

# Callback to download the graph as an image
@app.callback(
    Output('download_graph', 'data'),
    Input('download-button', 'n_clicks'),
    State('json_graph', 'data'),
    prevent_initial_call=True,
)
def download_graph(n_clicks, data):
    if not n_clicks:
        raise PreventUpdate
    
    print('download button clicked')
    fig_data = json.loads(data)
    fig = go.Figure(fig_data)
    fig.show()
    return (
    dcc.send_bytes(fig.write_image, "histogram.png"))

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

I’d like to make amends for my previous dumb answer by finding a solution, but regret I can’t.

If you are getting an error message it would be very useful to know what that is. When I run your code I get ValueError: Failed to start Kaleido subprocess followed by some further info. But I don’t know how to solve it.

There is discussion of a similar issue here:

@davidharris I don’t get any error at all. The tab name changes to ‘Updating …’ and that’s it.
Will update if there’s anything new.

did you check your Downloads folder? The dcc.Download component will use the browser settings for downloading files to decide whether to show a folder selection screen. If the setting is set to save files automatically to the Downloads folder, the downloaded file will be automatically saved without confirmation window.

As far as I have seen up until now, this behavior cannot be adjusted from the Dash code.

@Tobs ,

yes. the picture is not downloaded. If it were (and not visible from the browser itself), the tab name would change back from Updating …, which it doesn’t.

Hi @subodhpokhrel7,

I was able to reproduce the issue. I have no idea what is causing the issue, it seems either the kaleido engine takes a very long time to process the image, or something is wrong. I did have a virtual environment installed and installed kaleido there using pip install kaleido, which is different than the pip install -U kaleido that the docs suggested. Perhaps it needs access to kaleido in the Path variable, but then would it even work from a virtual environment? I don’t know.

I am too unfamiliar with exporting as image to help you here. @adamschroeder is there someone from the Plotly team that can help with the exporting as png using either go.figure.to_image or go.Figure.write_image?

P.S. @subodhpokhrel7 you have fig.show() in your download callback, that should not be there. That is used when showing plots outside of Dash. In the Dash framework, you use the dcc.Graph object to show the plot (which you are already doing). You can just delete that line to fix this.

Hey all,

Kaleido and windows are a bad combination. There are some threads about this. Downgrading to an older version of kaleido helps sometimes, see also

@AIMPED what would be the better alternative for Windows? I tried using Orca, but I had some errors/warnings that I had to set it up on the Path as well. I stopped trying at that point.

I’ve been digging around a bit - kaleido and orca both seem problematic - and have had some success using Playwright to convert the output of fig.write_html() to an image.

This version of your callback is working for me. (System reboot may be needed - whatever the other thing was doing seemed to break my browser in a way that caused the problem to reoccur with this approach until rebooted).

from playwright.sync_api import sync_playwright
from base64 import b64encode

...

@app.callback(
    Output('download_graph', 'data'),
    Input('download-button', 'n_clicks'),
    State('json_graph', 'data'),
    prevent_initial_call=True,
)
def download_graph(n_clicks, data):
    if not n_clicks:
        raise PreventUpdate
    
    fig_data = json.loads(data)
    fig = go.Figure(fig_data)
    fig_html = fig.to_html()

    with sync_playwright() as p:
        browser = p.chromium.launch()
        page = browser.new_page()
        page.set_content(fig_html)
        pngbytes = page.screenshot()

    encoded = b64encode(pngbytes).decode()
    contents = {"base64":True, "content":encoded, "filename":"download.png"}
    return (contents)

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

Changing the version to 0.1.0.post1 (pip install -U kaleido==0.1.0.post1) did work. However, there in an error in the MWE (but not in the application I’ve built, which is too complicated to upload here).

@Tobs regarding fig.show(), I only kept it there to make sure that the issue was with the downloading part and not re-generation of the picture in the funtion download_graph()

Not that I know of, unfortunately. I stopped trying too :slight_smile: