List of currently visible traces on plots generated by user input

Hello everyone,

Following a previous post on a similar topic (please see Original topic) , I have made some progress and face a new challenge which I can’t crack. Thank you @tcbegley for bringing me this far with his original solution :slight_smile:

I am trying to keep track of which traces are currently visible on several graphs. These graphs are generated after some user input to my dash applications (it could be 1 graph or 10 graphs depending on the user input selection.)
I did not manage to adapt @tcbegley original solution to work with this more “dynamic” approach.

At the application start, there is a Div created which allocates memory and space for the graphs that will populate the children property based on the user input:

html.Div(id='container', children=[]),

How could I then write a callback that keeps track of the traces visible for each graph once the user is done ticking on and off the traces that should be visible?

Thank you for your thoughts on that case. Please let me know if the problem statement is unclear or if any additional information could help to start a discussion.

R3s0luti0n

I think the best approach would be to replicate the previous answer using pattern-matching callbacks and a store for each generated graph. Could look something like this

import numpy as np
import plotly.graph_objs as go
from dash import ALL, MATCH, Dash, Input, Output, State, dcc, html


def make_figure_with_store(count):
    # makes a figure with associated store to keep track of visisble items
    fig = go.Figure(
        data=[
            go.Scatter(
                x=np.arange(10),
                y=np.random.rand(10),
                name=f"Name {i}",
            )
            for i in range(3)
        ]
    )
    return html.Div(
        [
            dcc.Graph(id={"type": "graph", "index": count}, figure=fig),
            dcc.Store(
                id={"type": "store", "index": count},
                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"])
                ],
            ),
        ]
    )


app = Dash()

app.layout = html.Div(
    [
        # create a store to keep track of how many graphs we've created so that
        # we can set the id of the graph appropriately
        dcc.Store(id="count-store", data=0),
        html.Button("Show items", id="show-items"),
        html.Div(id="output"),
        html.Button("Add graph", id="add-graph"),
        html.Div(id="graphs"),
    ]
)


@app.callback(
    [Output("graphs", "children"), Output("count-store", "data")],
    [Input("add-graph", "n_clicks")],
    [State("graphs", "children"), State("count-store", "data")],
)
def add_graph(n, graphs, count):
    # add a graph to the layout and increment the store count
    if n:
        graphs = graphs or []
        graphs.append(make_figure_with_store(count))
        count += 1
        return graphs, count
    return graphs, count


# when the legend is clicked, use restyleData to update the store
@app.callback(
    Output({"type": "store", "index": MATCH}, "data"),
    Input({"type": "graph", "index": MATCH}, "restyleData"),
    [
        State({"type": "store", "index": MATCH}, "data"),
        State({"type": "graph", "index": MATCH}, "id"),
    ],
)
def update_store(restyle_data, store, graph_id):
    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("show-items", "n_clicks"),
    State({"type": "store", "index": ALL}, "data"),
)
def show_legend_items(n, stores):
    if n:
        list_items = [
            html.Li(
                [
                    f"Graph {i}",
                    html.Ul(
                        [
                            html.Li(trace["name"])
                            for trace in store
                            if trace["visible"]
                        ]
                    ),
                ]
            )
            for i, store in enumerate(stores)
        ]
        return html.Ul(list_items)
    return html.P("Select items on the legend then click on the button")


if __name__ == "__main__":
    app.run_server(debug=True)
1 Like

Thank you very much for you answer @tcbegley . This looks like what I need to start moving forward. I have to adapt my current application structure to try and fit your solution. I will get started on my coding and get back to you with the final status :slight_smile:

Thank you very much for this push allowing me to move forward!

/R3s0luti0n

1 Like

@tcbegley I have now had time to dive into your solution. I do understand the logic of it and tried to implement an adapted version for my use case. However, it seems that I face an issue with properly wrapping my different figures/graphs into the children of the div (id=“graphs”).

The main difference with your solution is that I create all my figures in a for loop when a click button is triggered. I have adapted to not keep track of the figure generation count (as I do not think that I need it).
Therefore my graphs + stores generation callback looks like this:

# Make figures
@app.callback(
    
    # Outputs
    [
     Output("graphs", "children"),  # Adding the figures to the space reserved to them
    ],

    # Inputs
    [
     Input("add-graphs", "n_clicks"),  # Trigger this callback by clicking the "Generate figures" button
     Input("cache-data-store", "data")  # Feed with cached data which are retrieved and stored by a first user interaction prior to this one
    ],

    prevent_initial_call=True
)
def add_graphs(n_clicks, cache_data):
    # add a graph to the layout
    graphs = []
    blocks_count = 0

    if n_clicks:

        cache_data_object = cache_data

        for block in cache_data_object.data.keys():

            figure = make_graph_and_store(cache_data_object=cache_data_object, block=block, block_number=blocks_count, start_date=default_start_date, stop_date=default_stop_date, count=blocks_count)

            graphs.append(figure)

            blocks_count += 1   


    return graphs, blocks_count

My make_graph_and_store function returns a Div similar to the one you suggested:

 return html.Div(
    [
        dcc.Graph(id={"type": "graph", "index": count}, figure=figure),
        dcc.Store(
            id={"type": "store", "index": count},
            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"])
            ],
        ),
    ]
)

However when triggering that callback I run into this error:

Traceback (most recent call last):
  File "/home/thibaut/.local/lib/python3.9/site-packages/dash_extensions/enrich.py", line 532, in decorated_function
    return f(*args)
  File "/home/thibaut/Desktop/Dash_applications/appV6.py", line 331, in add_graphs
    figure = make_graph_and_store(cache_data_object=cache_data_object, block=block, block_number=blocks_count, start_date=default_start_date, stop_date=default_stop_date, count=blocks_count)
  File "/home/thibaut/Desktop/Dash_applications/appV6.py", line 187, in make_graph_and_store
    dcc.Graph(id={"type": "graph", "index": count}, figure=figure),
  File "/usr/lib/python3.9/site-packages/dash/development/base_component.py", line 388, in wrapper
    return func(*args, **kwargs)
  File "/usr/lib/python3.9/site-packages/dash/dcc/Graph.py", line 386, in __init__
    super(Graph, self).__init__(**args)
  File "/usr/lib/python3.9/site-packages/dash/development/base_component.py", line 139, in __init__
    raise TypeError(
TypeError: The `dcc.Graph` component (version 2.0.0) with the ID "{'type': 'graph', 'index': 0}" detected a Component for a prop other than `children`
Did you forget to wrap multiple `children` in an array?
Prop figure has value Graph(id={'name': 'block-0-figure', 'type': 'dates-dependant-figure', 'start_date': '2000-01-01 00:00:00', 'stop_date': '2021-10-18'}, config={'displaylogo': False, 'responsive': True}, figure=Figure({
    'data': [{'name': 'B329A_IB0S10_01_CO001_R001_NEW',
              'type': 'scatter',
              'x': array([datetime.datetime(2021, 5, 31, 11, 15),
                          datetime.datetime(2021, 5, 31, 11, 30),
                          datetime.datetime(2021, 5, 31, 11, 45), ...,
                          datetime.datetime(2021, 10, 17, 23, 15),
                          datetime.datetime(2021, 10, 17, 23, 30),
                          datetime.datetime(2021, 10, 17, 23, 45)], dtype=object),
              'y': array([430.13335164, 430.93333944, 428.33335368, ..., 415.93331909,
                          414.        , 416.79999797]),
              'yaxis': 'y'},
             {'name': 'B329A_IB0S10_01_CO001_SV_R001_NEW',
              'type': 'scatter',
              'x': array([datetime.datetime(2021, 5, 31, 11, 15),
                          datetime.datetime(2021, 5, 31, 11, 30),
                          datetime.datetime(2021, 5, 31, 11, 45), ...,
                          datetime.datetime(2021, 10, 17, 23, 15),
                          datetime.datetime(2021, 10, 17, 23, 30),
                          datetime.datetime(2021, 10, 17, 23, 45)], dtype=object),
              'y': array([1000., 1000., 1000., ..., 1000., 1000., 1000.]),
              'yaxis': 'y2'},
             {'name': 'B329A_IB0S10_01_MV101_S_R001_NEW',
              'type': 'scatter',
              'x': array([datetime.datetime(2021, 5, 31, 11, 15),
                          datetime.datetime(2021, 5, 31, 11, 30),
                          datetime.datetime(2021, 5, 31, 11, 45), ...,
                          datetime.datetime(2021, 10, 17, 23, 15),
                          datetime.datetime(2021, 10, 17, 23, 30),
                          datetime.datetime(2021, 10, 17, 23, 45)], dtype=object),
              'y': array([ 0.        ,  0.        ,  0.        , ..., 32.79535421, 29.95842489,
                          26.90945943]),
              'yaxis': 'y3'},
             {'name': 'B329A_IB0S10_01_VM101_R001_NEW',
              'type': 'scatter',
              'x': array([datetime.datetime(2021, 5, 31, 11, 15),
                          datetime.datetime(2021, 5, 31, 11, 30),
                          datetime.datetime(2021, 5, 31, 11, 45), ...,
                          datetime.datetime(2021, 10, 17, 23, 15),
                          datetime.datetime(2021, 10, 17, 23, 30),
                          datetime.datetime(2021, 10, 17, 23, 45)], dtype=object),
              'y': array([20., 20., 20., ..., 10., 10., 10.]),
              'yaxis': 'y4'},
             {'name': 'B329A_IB0S10_01_TR001_R001_NEW',
              'type': 'scatter',
              'x': array([datetime.datetime(2021, 5, 31, 11, 15),
                          datetime.datetime(2021, 5, 31, 11, 30),
                          datetime.datetime(2021, 5, 31, 11, 45), ...,
                          datetime.datetime(2021, 10, 17, 23, 15),
                          datetime.datetime(2021, 10, 17, 23, 30),
                          datetime.datetime(2021, 10, 17, 23, 45)], dtype=object),
              'y': array([22.60000038, 22.93333308, 22.8333327 , ..., 22.13333384, 22.10000038,
                          22.06666692]),
              'yaxis': 'y5'},
             {'name': 'B329A_IB0S10_01_TR001_R001_KØL_KALK_SV_NEW',
              'type': 'scatter',
              'x': array([datetime.datetime(2021, 5, 31, 11, 15),
                          datetime.datetime(2021, 5, 31, 11, 30),
                          datetime.datetime(2021, 5, 31, 11, 45), ...,
                          datetime.datetime(2021, 10, 17, 23, 15),
                          datetime.datetime(2021, 10, 17, 23, 30),
                          datetime.datetime(2021, 10, 17, 23, 45)], dtype=object),
              'y': array([23., 23., 23., ..., 26., 26., 26.]),
              'yaxis': 'y6'},
             {'name': 'B329A_IB0S10_01_TR001_R001_VAR_KALK_SV_NEW',
              'type': 'scatter',
              'x': array([datetime.datetime(2021, 5, 31, 11, 15),
                          datetime.datetime(2021, 5, 31, 11, 30),
                          datetime.datetime(2021, 5, 31, 11, 45), ...,
                          datetime.datetime(2021, 10, 17, 23, 15),
                          datetime.datetime(2021, 10, 17, 23, 30),
                          datetime.datetime(2021, 10, 17, 23, 45)], dtype=object),
              'y': array([21., 21., 21., ..., 22., 22., 22.]),
              'yaxis': 'y7'}],
    'layout': {'colorway': [#1F77B4, #FF7F0E, #2CA02C, #D62728, #9467BD, #8C564B,
                            #E377C2, #7F7F7F, #BCBD22, #17BECF],
               'hovermode': 'x unified',
               'template': '...',
               'title': {'text': 'Block 0 figure'},
               'xaxis': {'domain': [0, 0.7]},
               'yaxis': {'position': 1.0,
                         'range': [0.0, 695.3466756184896],
                         'showline': True,
                         'tickfont': {'color': '#1F77B4'}},
               'yaxis2': {'overlaying': 'y',
                          'position': 0.96,
                          'range': [900.0, 1100.0],
                          'showline': True,
                          'tickfont': {'color': '#FF7F0E'}},
               'yaxis3': {'overlaying': 'y',
                          'position': 0.92,
                          'range': [0.0, 695.3466756184896],
                          'showline': True,
                          'tickfont': {'color': '#2CA02C'}},
               'yaxis4': {'overlaying': 'y',
                          'position': 0.88,
                          'range': [0.0, 695.3466756184896],
                          'showline': True,
                          'tickfont': {'color': '#D62728'}},
               'yaxis5': {'overlaying': 'y',
                          'position': 0.84,
                          'range': [18.0, 30.800000000000004],
                          'showline': True,
                          'tickfont': {'color': '#9467BD'}},
               'yaxis6': {'overlaying': 'y',
                          'position': 0.8,
                          'range': [18.0, 30.800000000000004],
                          'showline': True,
                          'tickfont': {'color': '#8C564B'}},
               'yaxis7': {'overlaying': 'y',
                          'position': 0.76,
                          'range': [18.0, 30.800000000000004],
                          'showline': True,
                          'tickfont': {'color': '#E377C2'}}}
}))
TypeError: The `dcc.Graph` component (version 2.0.0) with the ID "{'type': 'graph', 'index': 0}" detected a Component for a prop other than `children`
Did you forget to wrap multiple `children` in an array?
Prop figure has value Graph(id={'name': 'block-0-figure', 'type': 'dates-dependant-figure', 'start_date': '2000-01-01 00:00:00', 'stop_date': '2021-10-18'}, config={'displaylogo': False, 'responsive': True}, figure=Figure({
    'data': [{'name': 'B329A_IB0S10_01_CO001_R001_NEW',
              'type': 'scatter',
              'x': array([datetime.datetime(2021, 5, 31, 11, 15),
                          datetime.datetime(2021, 5, 31, 11, 30),
                          datetime.datetime(2021, 5, 31, 11, 45), ...,
                          datetime.datetime(2021, 10, 17, 23, 15),
                          datetime.datetime(2021, 10, 17, 23, 30),
                          datetime.datetime(2021, 10, 17, 23, 45)], dtype=object),
              'y': array([430.13335164, 430.93333944, 428.33335368, ..., 415.93331909,
                          414.        , 416.79999797]),
              'yaxis': 'y'},
             {'name': 'B329A_IB0S10_01_CO001_SV_R001_NEW',
              'type': 'scatter',
              'x': array([datetime.datetime(2021, 5, 31, 11, 15),
                          datetime.datetime(2021, 5, 31, 11, 30),
                          datetime.datetime(2021, 5, 31, 11, 45), ...,
                          datetime.datetime(2021, 10, 17, 23, 15),
                          datetime.datetime(2021, 10, 17, 23, 30),
                          datetime.datetime(2021, 10, 17, 23, 45)], dtype=object),
              'y': array([1000., 1000., 1000., ..., 1000., 1000., 1000.]),
              'yaxis': 'y2'},
             {'name': 'B329A_IB0S10_01_MV101_S_R001_NEW',
              'type': 'scatter',
              'x': array([datetime.datetime(2021, 5, 31, 11, 15),
                          datetime.datetime(2021, 5, 31, 11, 30),
                          datetime.datetime(2021, 5, 31, 11, 45), ...,
                          datetime.datetime(2021, 10, 17, 23, 15),
                          datetime.datetime(2021, 10, 17, 23, 30),
                          datetime.datetime(2021, 10, 17, 23, 45)], dtype=object),
              'y': array([ 0.        ,  0.        ,  0.        , ..., 32.79535421, 29.95842489,
                          26.90945943]),
              'yaxis': 'y3'},
             {'name': 'B329A_IB0S10_01_VM101_R001_NEW',
              'type': 'scatter',
              'x': array([datetime.datetime(2021, 5, 31, 11, 15),
                          datetime.datetime(2021, 5, 31, 11, 30),
                          datetime.datetime(2021, 5, 31, 11, 45), ...,
                          datetime.datetime(2021, 10, 17, 23, 15),
                          datetime.datetime(2021, 10, 17, 23, 30),
                          datetime.datetime(2021, 10, 17, 23, 45)], dtype=object),
              'y': array([20., 20., 20., ..., 10., 10., 10.]),
              'yaxis': 'y4'},
             {'name': 'B329A_IB0S10_01_TR001_R001_NEW',
              'type': 'scatter',
              'x': array([datetime.datetime(2021, 5, 31, 11, 15),
                          datetime.datetime(2021, 5, 31, 11, 30),
                          datetime.datetime(2021, 5, 31, 11, 45), ...,
                          datetime.datetime(2021, 10, 17, 23, 15),
                          datetime.datetime(2021, 10, 17, 23, 30),
                          datetime.datetime(2021, 10, 17, 23, 45)], dtype=object),
              'y': array([22.60000038, 22.93333308, 22.8333327 , ..., 22.13333384, 22.10000038,
                          22.06666692]),
              'yaxis': 'y5'},
             {'name': 'B329A_IB0S10_01_TR001_R001_KØL_KALK_SV_NEW',
              'type': 'scatter',
              'x': array([datetime.datetime(2021, 5, 31, 11, 15),
                          datetime.datetime(2021, 5, 31, 11, 30),
                          datetime.datetime(2021, 5, 31, 11, 45), ...,
                          datetime.datetime(2021, 10, 17, 23, 15),
                          datetime.datetime(2021, 10, 17, 23, 30),
                          datetime.datetime(2021, 10, 17, 23, 45)], dtype=object),
              'y': array([23., 23., 23., ..., 26., 26., 26.]),
              'yaxis': 'y6'},
             {'name': 'B329A_IB0S10_01_TR001_R001_VAR_KALK_SV_NEW',
              'type': 'scatter',
              'x': array([datetime.datetime(2021, 5, 31, 11, 15),
                          datetime.datetime(2021, 5, 31, 11, 30),
                          datetime.datetime(2021, 5, 31, 11, 45), ...,
                          datetime.datetime(2021, 10, 17, 23, 15),
                          datetime.datetime(2021, 10, 17, 23, 30),
                          datetime.datetime(2021, 10, 17, 23, 45)], dtype=object),
              'y': array([21., 21., 21., ..., 22., 22., 22.]),
              'yaxis': 'y7'}],
    'layout': {'colorway': [#1F77B4, #FF7F0E, #2CA02C, #D62728, #9467BD, #8C564B,
                            #E377C2, #7F7F7F, #BCBD22, #17BECF],
               'hovermode': 'x unified',
               'template': '...',
               'title': {'text': 'Block 0 figure'},
               'xaxis': {'domain': [0, 0.7]},
               'yaxis': {'position': 1.0,
                         'range': [0.0, 695.3466756184896],
                         'showline': True,
                         'tickfont': {'color': '#1F77B4'}},
               'yaxis2': {'overlaying': 'y',
                          'position': 0.96,
                          'range': [900.0, 1100.0],
                          'showline': True,
                          'tickfont': {'color': '#FF7F0E'}},
               'yaxis3': {'overlaying': 'y',
                          'position': 0.92,
                          'range': [0.0, 695.3466756184896],
                          'showline': True,
                          'tickfont': {'color': '#2CA02C'}},
               'yaxis4': {'overlaying': 'y',
                          'position': 0.88,
                          'range': [0.0, 695.3466756184896],
                          'showline': True,
                          'tickfont': {'color': '#D62728'}},
               'yaxis5': {'overlaying': 'y',
                          'position': 0.84,
                          'range': [18.0, 30.800000000000004],
                          'showline': True,
                          'tickfont': {'color': '#9467BD'}},
               'yaxis6': {'overlaying': 'y',
                          'position': 0.8,
                          'range': [18.0, 30.800000000000004],
                          'showline': True,
                          'tickfont': {'color': '#8C564B'}},
               'yaxis7': {'overlaying': 'y',
                          'position': 0.76,
                          'range': [18.0, 30.800000000000004],
                          'showline': True,
                          'tickfont': {'color': '#E377C2'}}}
}))

I simply do not understand what I am doing wrong here. Thoughts?

Thank you very much for dedicating time to helping me improve on understand dash!

BR,

/R3s0luti0n

It looks like you’re passing a dcc.Graph to the figure argument of another dcc.Graph. This could be happening either in your make_graph_and_store function or inside a callback if you have any callbacks that have Output("<graph-id>", "figure") as an output.

Basically search through your code for something equivalent to this

dcc.Graph(figure=dcc.Graph(go.Figure(...)))

and change it too

dcc.Graph(figure=go.Figure(...))
1 Like

Thanks for pointing out that mistake @tcbegley . I have indeed restructured my code and found that I had some terrible mistakes in there.

Your implementation now seems to work (almost). I have managed to generate my figures as I wanted as well as implement the count-store to keep track of each “figure block”. The issue is that the show_legend_items callback does not work properly. It works fine the first time to show everything that is originally plotted on my different graphs, but when ticking on/off some lines and re-click the show_item button, it does not update the text displayed. Therefore I suspect the update_store callback to not work properly.

The overall structure of my application is as follow:

  1. The application starts
  2. The user selects a configuration file from a drop-down menu: This triggers a function to “cache-store” some data on the server side
  3. The user clicks on a button to generate the figures
  4. The user can view what lines are plotted on each graph by clicking another button
  5. Finally the user can export what lines are plotted on each graph as a .csv file

My implementation looks like this for the layout:

output = []

output.extend([

    # Main title
    html.H1(children='Custom time-series visualization',
        style={'textAlign': 'center'}
        ),

    # Configuration file dropdown menu
    html.Div(

        # Dash component
        dcc.Dropdown(id='dropdown-input-filename',
            options=[{'label': i, 'value': i} for i in files],
            searchable=False,
            clearable=True,
            placeholder="Select an input configuration file",
            ),

        # CSS styling
        style={"width": "25%", 
            "padding": 50,
            },
        
        ),

    # Server-side cache memory store
    dcc.Loading(
        
        # Dash component
        dcc.Store(id='cache-data-store'),
        fullscreen=True,
        type="cube"
        
        ),

    # create a store to keep track of how many graphs we've created so that
    # we can set the id of the graph appropriately
    dcc.Store(id="count-store", data=0),

    # Space allocation for graph generation
    html.Button("Generate figures", id="add-graphs"),
    html.Div(id="graphs"),

    # Print button for showing what traces are visible on each block/plot
    html.Button("Summary of plotted lines", id="show-items", style={"margin-top": "15px"}),
    html.Div(id="output"),

    html.Div([html.Button("Click to export selected channels list as csv file", id="export_csv_button"), dcc.Download(id="download-channels-csv")]),

    ])

This gives an overview of what my make_graph_and_store function looks like:

def make_graph_and_store(cache_data_object, block, block_number, start_date, stop_date, count):
    # Makes some stuff with the data
    temp_figure = go.Figure(data=plotly_data, layout=go.Layout(**layout_kwargs))

    #temp_figure.update_traces(mode="markers+lines", hovertemplate=None)
    temp_figure.update_layout(hovermode="x unified")

    ### Figure creation

    figure = dcc.Graph(id={
            "name": "block-{0}-figure".format(block_number),
            "type": "graph_figure",
            "start_date": start_date,
            "stop_date": stop_date ,
            "index": count
        }, 
        figure=temp_figure, 
        config={"displaylogo": False,
            "responsive": True
        }
    )

    return html.Div(
    [
        #dcc.Graph(id={"type": "graph_figure", "index": count}, figure=figure),
        figure,
        dcc.Store(
            id={"type": "graph_store", "index": count},
            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(temp_figure["data"])
            ],
        ),
    ]
)

The different callbacks:

# Query data from MS Azure and store it on local server
@app.callback(

    # Output
    ServersideOutput("cache-data-store", "data"),

    # Input
    Input("dropdown-input-filename", "value")
)
def query_data(value):
    time.sleep(1)
    
    CWD = os.getcwd()
    input_data_path = CWD + "/Inputs"
    file_path = input_data_path + "/{0}".format(value)

    cache_data_object =  DataCachingClassV2(input_csv=file_path, start_date=default_start_date, stop_date=default_stop_date)

    return cache_data_object  # no JSON serialization here


# Make figures
@app.callback(
    
    # Outputs
    [
     Output("graphs", "children"),  # Adding the figures to the space reserved to them
     Output("count-store", "data")  # Logging of the number of figures created to have them indexed by ID
    ],

    # Inputs
    [
     Input("add-graphs", "n_clicks"),  # Trigger this callback by clicking the "Generate figures" button
     Input("cache-data-store", "data")  # Feed with cached data
    ],

    # States
    [
     #State("cache-data-store", "data"),
     State("graphs", "children"),
     State("count-store", "data")
    ],

)
def add_graphs(n_clicks, cache_data, graph_figures, count):

    if n_clicks:

        graph_figures = graph_figures or []
        count = 1

        cache_data_object = cache_data

        # add a graph to the layout and increment the store count
        for block in cache_data_object.data.keys():

            figure = make_graph_and_store(cache_data_object=cache_data_object, block=block, block_number=count, start_date=default_start_date, stop_date=default_stop_date, count=count)

            graph_figures.append(figure)

            count += 1   

        return graph_figures, count

    return graph_figures, count


# when the legend is clicked, use restyleData to update the store
@app.callback(
    Output({"type": "graph_store", "index": MATCH}, "data"),
    Input({"type": "graph_figure", "index": MATCH}, "restyleData"),
    [
        State({"type": "graph_store", "index": MATCH}, "data"),
        State({"type": "graph_figure", "index": MATCH}, "id"),
    ],
)
def update_store(restyle_data, graph_store, graph_id):

    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 graph_store




# when the button is clicked, use the store to populate a list (or do something
# more interesting)
@app.callback(
    Output("output", "children"),
    Input("show-items", "n_clicks"),
    State({"type": "graph_store", "index": ALL}, "data"),
)
def show_legend_items(n, stores):
    if n:
        list_items = [
            html.Li(
                [
                    f"Block {i+1} graph",
                    html.Ul(
                        [
                            html.Li(trace["name"])
                            for trace in store
                            if trace["visible"]
                        ]
                    ),
                ]
            )
            for i, store in enumerate(stores)
        ]
        return html.Ul(list_items)
    return html.P("Select items on the legend then click on the button")
`
The only difference I see is on my ***update_store*** function callback where I tuned the different store types to match my current structure. Did I miss something obvious about how the MATCH method works? 

Thank you again for your time and help! 

Best regards,

R3s0luti0n

Hi again @tcbegley , I still have not managed to get your solution to "dynamically " work …
I have looked through the entire implementation again and cannot spot what causes it to work only once and not update afterwards when the user interacts with the plots lines.

Any further thoughts on how to start debugging? :slight_smile:

BR,

R3s0luti0n