Fun things to do with Graph configurations in Dash

CodingData science

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.

I’m sorry, this is not the grainy crap I signed up for!

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.

Ah. Much better. But still not 100% there.

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 Plotly Mode Bar controls, full set. For 3D plots, replace 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.

Note the brightly coloured Plotly icon to the right.

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.

Chris von Csefalvay
Data scientist by day, computational epidemiologist by night, constantly sleep-deprived husband and dad to the world's most adorable Golden Retriever puppy.

You may also like

Leave a Reply