List of currently visible traces on a plot - Python 3.9.6

Hello everyone,

I am fairly new to Dash and plotly so please bear with me.
I have built a little dash application using python, for showing several time-series on a single plot. Plotly offers a very nice legend feature that allows the user to toggle on/off the different traces which are plotted.
I would be interested in having a “dynamic” list, tracking which traces are currently visible on the graph in order to be able to export that list to a .csv file when the user is done ticking on and off the traces that should be visible on the graph.

Any ideas about where to start?

Thank you very much for your time and help :slight_smile:

/R3s0luti0n

You can use the restyleData prop of the Graph to detect when the legend has been interacted with.

The thing that’s slightly complicated is that it only shows you changes, not the current state of the legend. I can’t see how you would pull that out from the figure attribute of the Graph, so the best I can think to do is to keep track of the current state of the legend yourself by watching for edits.

Here’s an example of that. It just displays the selected items in a list when the user clicks on a button, but you could easily adapt this to download a CSV or similar instead.

import dash
import dash_core_components as dcc
import dash_html_components as html
import numpy as np
import plotly.graph_objs as go
from dash.dependencies import Input, Output, State

FIG = go.Figure(
    data=[
        go.Scatter(x=np.arange(10), y=np.random.rand(10), name=f"Name {i}")
        for i in range(10)
    ]
)

app = dash.Dash()

app.layout = html.Div(
    [
        # create a store which will keep track of which elements in the legend
        # are currently active
        dcc.Store(
            id="store",
            data=[
                {
                    "name": d.name if d.name is not None else f"trace {i}",
                    "visible": d.visible if d.visible is not None else True,
                }
                for i, d in enumerate(FIG["data"])
            ],
        ),
        dcc.Graph(id="graph", figure=FIG),
        html.Button("Button", id="button"),
        html.Div(id="output"),
    ]
)


# when the legend is clicked, use restyleData to update the store
@app.callback(
    Output("store", "data"),
    Input("graph", "restyleData"),
    [State("store", "data")],
)
def update_store(restyle_data, store):
    if restyle_data is not None:
        edits, indices = restyle_data
        try:
            for visible, index in zip(edits["visible"], indices):
                # visible could be the string "legend_only" which is truthy
                # hence explicit comparison to True here
                store[index]["visible"] = visible is True
        except KeyError:
            pass

    return store


# when the button is clicked, use the store to populate a list (or do something
# more interesting)
@app.callback(
    Output("output", "children"),
    Input("button", "n_clicks"),
    State("store", "data"),
)
def show_legend_items(n, store):
    if n:
        return html.Ul(
            [html.Li(item["name"]) for item in store if item["visible"]]
        )
    return html.P("Select items on the legend then click on the button")


if __name__ == "__main__":
    app.run_server(debug=True)
2 Likes

Thank you very much for such a detailed answer to my question. It looks like this is what I am looking for. I will doodle around and try to implement your suggestion. :grinning:

1 Like