So there you are, with your gorgeous Dash app, generating gorgeous plots. And because this is the 21st century, you use Plotly’s built-in plot download functionality (little camera in the hover bar). And wow, does it look bad.

Have you ever been there? I know I have. Moe, the Measles Outbreak Explorer, is an app I built to visualise the data from the measles outbreak currently ravaging Samoa. I use the download functionality to quickly grab some images to include in tweets… and they really weren’t all that pretty in terms of resolution. Worse, as data piles up, the square aspect ratio is proving increasingly cramped for the temporal plots. These convey important information. They should be able to do so efficiently.
Configuring your Dash application’s export size
Fortunately, since a recent fix in Plotly that was exposed to Dash, this can all be easily fixed! The dcc.Graph
object now takes an argument, config
, that gives you granular control of the plot object.
To fix your plot size, simply pass in a dictionary with your desired height and width values (in pixels). Note that this will only affect the download, not the plot size itself!
PLOT_RESOLUTION:dict = dict(width=1024, height=768) dcc.Graph(figure=fig, config={"toImageButtonOptions": PLOT_RESOLUTION})
Keep in mind that this will affect the plot size, but not other hard-coded sizes you might have. In particular, any graph_objects.layout.Annotation
objects (see documentation here) you might have passed into your plot might not scale. Depending on your outlook, this may be a feature or a bug.

Don’t forget that if you rescale your plot, certain other things will scale with it. For instance, in the plot shown above, the fixed manual padding before the CFR indicator is normally safely below the red line indicating mortality. However, the aspect ratio change has changed all that, and now we’ll have to work around the mortality line obscuring the CFR.
The Mode Bar: always there for you
The author apologises for the cheesy Friends reference.
By default, Plotly provides users with a set of tools – zooming in, lasso selection, export, the whole kitchen sink. These are displayed in what Plotly calls the Mode Bar. Ordinarily, on non-touch devices, this appears if you hover over the plot area. You can keep the Mode Bar permanently present by configuring the Graph object:
dcc.Graph(figure=fig, config={"displayModeBar": True}
Don’t touch that!
You may also want to limit your users in what tools they can use. You may, for instance, be particularly evil and not want to give them the option to zoom in or out. This is accomplished by passing in a list
to the modeBarButtonsToRemove
property within config
:
dcc.Graph(figure=fig, config={"modeBarButtonsToRemove": ["zoomIn2d", "zoomOut2d"]}
Of course, this requires you to know what each of the buttons are called – which is not trivial. The guide below should help:

2d
with 3d
.As a general rule, I like removing the autoscale feature – autoscaling can be quite hit and miss, and at times, the scale is part of the message of your plot. The same goes for the somewhat misleading home button. After several years working with Plotly, I think I can admit without losing (too much) face that for the longest time I had no idea what that button actually did.
White labelling your plot
Ethics and Niceness Note: Plotly is free software, and the folks who are working on it are really, really great people. They could have altogether removed the ability to remove the Plotly logo from the Mode Bar, but they didn’t. As such, I only recommend white labelling your plot if you are using an enterprise version or if you absolutely, positively have to.
By default, the Mode Bar shows a tiny Plotly icon, which shows ‘Produced with Plotly’ when hovering over it and link to the Plotly website.

You can hide this by setting the displaylogo
configuration setting to False
. As said – only do this if you absolutely have to.
Putting it all together
Especially if you have a Dash application with multiple plots, it helps to have a consistent graph configuration. My preferred design pattern is to have a nested generator. In a nested generator, a single plot generator function hosts all shared properties, and configures all plots accordingly. Below is an abbreviated example of what this looks like in practice:
def generate_plot(plot_type: str, data: pd.DataFrame, plot_resolution: tuple=(1024, 768), white_label: bool=True, restrict_spike_lines: bool=True, **kwargs): config = dict(**kwargs) config["toImageButtonOptions"] = {"width": plot_resolution[0], "height": plot_resolution[1]} config["displaylogo"] = (not white_label)) if restrict_spike_lines: if "modeBarButtonsToRemove" in config: config["modeBarButtonsToRemove"].append("toggleSpikeLines") else: config["modeBarButtonsToRemove"] = ["toggleSpikeLines"] if plot_type is "plot_type_1": fig = ### Put generating code here elif plot_type is "plot_type_2": fig = ### Put generating code for the second plot type here elif plot_type is "plot_type_3": fig = ### Put generating code for the third plot type here else: raise KeyError(f"Plot type {plot_type} is not defined.") return dcc.Graph(figure=fig, config=config)
Of course, if you have a simpler codebase, you might simply put the configuration into a single dict
you declare as a constant, or directly feed it into your dcc.Graph
call – just as long as you remember you can put an arbitrary amount of settings into your config call.
And, of course, once you’re done, don’t forget to redeploy – preferably using the super-fast, super-simple ECS based system described in my previous post.