Learn how to use Dash Bio for next-gen sequencing & quality control. 🧬Register for the Oct 27 webinar.

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