Dash - callback on legend item clicked

I have a graph with a few traces plotted on it.
Im trying to setup a callback function which will be triggered every time a legend item is clicked on, and will print the names of traces which are currently visible.

digging in the documentation, i haven’t succeded achieving it (not enough javascript knowledge apparently)

can someone please provide a code\source\idea for how to accomplish it?

1 Like

Hi @avivazran welcome to the forum! Changing the visibility of a trace in the legend triggers a restyleData event as in the minimal app below. You can listen to this event in your callback.

import json
from textwrap import dedent as d

import dash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output

external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']

app = dash.Dash(__name__, external_stylesheets=external_stylesheets)

styles = {
    'pre': {
        'border': 'thin lightgrey solid',
        'overflowX': 'scroll'
    }
}

app.layout = html.Div([
    dcc.Graph(
        id='basic-interactions',
        config={'editable':True},
        figure={
            'data': [
                {
                    'x': [1, 2, 3, 4],
                    'y': [4, 1, 3, 5],
                    'text': ['a', 'b', 'c', 'd'],
                    'customdata': ['c.a', 'c.b', 'c.c', 'c.d'],
                    'name': 'Trace 1',
                    'mode': 'markers',
                    'marker': {'size': 12}
                },
                {
                    'x': [1, 2, 3, 4],
                    'y': [9, 4, 1, 4],
                    'text': ['w', 'x', 'y', 'z'],
                    'customdata': ['c.w', 'c.x', 'c.y', 'c.z'],
                    'name': 'Trace 2',
                    'mode': 'markers',
                    'marker': {'size': 12}
                }
            ],
            'layout': {
                'clickmode': 'event+select'
            }
        }
    ),

    html.Div(className='row', children=[
        html.Div([
            dcc.Markdown(d("""
                **Click Data**

                Click on points in the graph.
            """)),
            html.Pre(id='click-data', style=styles['pre']),
        ], className='three columns'),
    ])
])


@app.callback(
    Output('click-data', 'children'),
    [Input('basic-interactions', 'restyleData')])
def display_restyle_data(restyleData):
    return json.dumps(restyleData, indent=2)


if __name__ == '__main__':
    app.run_server(debug=True)

3 Likes

Hi Emmanuelle, thanks for your answer!

However, I couldn’t help but notice that the data your Pre section is displaying does not exactly provide what avivazran (and I) are looking for: Output which displays which categories are currently visible.

Output of the restyleData solely provides information on which category was turned off/on most recently. Is there a way to generate an output which provides a complete list of the categories and their visibility status? (meaning: If the legend/color variable consists of three countries (“USA”, “GER”, “RUS”), outputting or updating a dictionary like that: {“GER”: True|False, “GER”: True|False, “USA”: True|False, “RUS”: True|False})

hope the question was clear!

1 Like

Is there a way to generate an output which provides a complete list of the categories and their visibility status? (meaning: If the legend/color variable consists of three countries (“USA”, “GER”, “RUS”), outputting or updating a dictionary like that: {“GER”: True|False, “GER”: True|False, “USA”: True|False, “RUS”: True|False})

Were you ever able to figure this out? Seems weird that THIS wouldn’t be the kind of thing you’d get from 'restyleData'

What is d in this line dcc.Markdown(d("""? it gives me an error d is not defined!

Sharing in case anyone is still looking for an answer: I ended up changing the callback so that it’s still activated on restyleData, but grab the required components from ‘figure’ instead.

Note: this will not run on the first load of the page, but will show names of active traces after the first click on the legend:

import json
from textwrap import dedent as d

import dash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output, State

external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']

app = dash.Dash(__name__, external_stylesheets=external_stylesheets)

styles = {
    'pre': {
        'border': 'thin lightgrey solid',
        'overflowX': 'scroll'
    }
}

app.layout = html.Div([
    dcc.Graph(
        id='basic-interactions',
        config={'editable':True},
        figure={
            'data': [
                {
                    'x': [1, 2, 3, 4],
                    'y': [4, 1, 3, 5],
                    'text': ['a', 'b', 'c', 'd'],
                    'customdata': ['c.a', 'c.b', 'c.c', 'c.d'],
                    'name': 'Trace 1',
                    'mode': 'markers',
                    'marker': {'size': 12}
                },
                {
                    'x': [1, 2, 3, 4],
                    'y': [9, 4, 1, 4],
                    'text': ['w', 'x', 'y', 'z'],
                    'customdata': ['c.w', 'c.x', 'c.y', 'c.z'],
                    'name': 'Trace 2',
                    'mode': 'markers',
                    'marker': {'size': 12}
                }
            ],
            'layout': {
                'clickmode': 'event+select'
            }
        }
    ),

    html.Div(className='row', children=[
        html.Div([
            html.Pre(id='click-data', style=styles['pre']),
        ], className='three columns'),
    ])
])


@app.callback(
    Output('click-data', 'children'),
    Input('basic-interactions', 'restyleData'), 
    State('basic-interactions', 'figure')
)
def display_restyle_data(restyleData, figure):
#     return json.dumps(figure, indent=2)
    return [x['name'] for x in figure['data'] if 'visible' in x.keys() and x['visible'] == True]


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

Thanks a lot!

To add a bit, I had some issues with this working for initial states. So when the graph is first rendered with all data, this was not reflected in callback.

Aka it rendered wrong values on the first select and deselection of legends.

However adding “visible”: True, to the initial layout ensured that the callback catched the initial visible states :slight_smile: