I have a callback in my Dash app that grabs a large figure (scatter_3d plot with many animation frames) that was generated in a previous background callback and saves it as an interactive HTML file. That’s all working fine, however the callback to save the figure is slow since it has to make a round trip to the server with lots of data.
I’d like to switch this to use a clientside callback to save the figure instead, but I can’t find any documentation on how to do something like that using the plotly.js side of things. The output HTML file from the existing callback is ~80MB, otherwise I would just precompute and put it in a clientside store that I can download from as described in the docs.
For reference, here’s what the callback I have now looks like (figure-download is a dcc.Download and 3d-plot is the dcc.Graph):
I think that if you want to do it clientside, then you’ll have to code your own .to_html() function. The best option to me seems to find the already generated HTML content of #3d-plot and copy it.
clientside_callback(
"""function() {
// Get the figure
var fig = document.getElementById("3d-plot");
// Get the HTML ?
var html_content = fig.innerHTML();
// Next : create a blob or a link with your content
// so that it triggers a download. keywords for search: javascript blob content download
return dash_clientside.no_update;
}
""",
Input("download-button", "n_clicks"),
Input("download-button", "n_clicks"),
)
(code not tested).
Another solution is to store your figure content in a global variable
# At creation time, you can just save the content in a global variable, or on the Disk
fig_data = ...
# Then you get rid of the State (which is causing you yo upload 80MB of data)
# But the user will still have to download again 80MB of data.
@callback(
Output("figure-download", "data"),
Input("download-button", "n_clicks"),
prevent_initial_call=True
)
def download(n_clicks):
if n_clicks:
fig = go.Figure(fig_data)
return dict(content=fig.to_html(), filename="figure.html")
This solution however is not safe for multiple users / multiple figure data, and will not work if your Dash app is deployed with gunicorn and multiple workers/threads, etc. I think the clientside solution is better.
I tried grabbing the HTML of the figure itself as you suggested, but unfortunately it looks like the data is not actually stored directly in that HTML, so the .to_html() call must be doing something different. You can see this if you inspect the plot using the dev tools in the browser. So the output HTML I got looked like my figure, just the data was not there so nothing was actually displayed.
I think your point is correct about having to build up the HTML myself if I want to do a clientside callback. However, it may take a bit more work to pull the data from wherever it is stored in the page and successfully assemble it into a standalone figure. Maybe I can poke around in the repo and see what .to_html() is actually doing.
I have another page that is doing server-side caching of data in Redis, so I could do something similar to make your second solution work for multiple users by just caching the HTML content at figure creation time and using that in my callback instead.