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)