Black Lives Matter. Please consider donating to Black Girls Code today.

Get trace name from clickdata

I have been developing my first dash app over the past several days and have been blown away by the flexibility and functionality! One issue I am currently running into is how to access the trace name from clickData. I have searched the forum and attempted to implement the solution outlined here. However, I am receiving the following error:

AttributeError: 'Graph' object has no attribute 'figure'

I believe that the issue I am running into is I am returning the figure attribute through a callback. See below for the exact implementation:

pertinent app layout:

html.Div(
    className = 'six columns',
    style = {'border': '1px solid red'},
    children = [
        dcc.Graph(
            id = 'source_count_bar'
        )
    ]
)

callbacks:

@app.callback(
    dash.dependencies.Output('sentiment_graph', 'figure'),
    [dash.dependencies.Input('body_title_radio', 'value'),
     dash.dependencies.Input('week_month_radio', 'value'),
     dash.dependencies.Input('trump_clinton_checklist', 'values')])
def update_sentiment(body_title, week_month, trump_clinton):
    if body_title == 'body':
        sentiment_data = article_sentiment_dict
    else:
        sentiment_data = title_sentiment_dict

    traces = []
    for i in trump_clinton:
        traces.append(go.Scatter(
            x = list(sentiment_data[i][week_month].keys()),
            y = list(sentiment_data[i][week_month].values()),
            mode = 'lines+markers',
            name = i)
        )

    return {
        'data': traces,
        'layout': {
            'height': 400,
            'margin': {
                'l':40,
                'b':80,
                't':20,
                'r':0
                },
            'clickmode': 'event+select'
        }
    }


@app.callback(
    dash.dependencies.Output('word_count_bar', 'figure'),
    [dash.dependencies.Input('body_title_radio', 'value'),
     dash.dependencies.Input('week_month_radio', 'value'),
     dash.dependencies.Input('sentiment_graph', 'clickData')])
def update_word_count(body_title, week_month, clickData):
    if body_title == 'body':
        word_count_data = article_wordcount_dict
    else:
        word_count_data = title_wordcount_dict

    curve_number = clickData['points'][0]['curveNumber']
    trace_name = app.layout['sentiment_graph'].figure['data'][curve_number]['name']
    date = str(clickData['points'][0]['x'])
    print(date, trace_name)
    bars = [go.Bar(
                x = list(word_count_data[trace_name][week_month][date].keys()),
                y = list(word_count_data[trace_name][week_month][date].values())
    )]
    return {
        'data': bars
    }

For those that stumble onto this later - this was a case of making things more difficult than they need to be. See below for the solution:

@app.callback(
    dash.dependencies.Output('word_count_bar', 'figure'),
    [dash.dependencies.Input('body_title_radio', 'value'),
     dash.dependencies.Input('week_month_radio', 'value'),
     dash.dependencies.Input('trump_clinton_checklist', 'values'),
     dash.dependencies.Input('sentiment_graph', 'clickData')]
)
def update_word_count(body_title, week_month, checklist, clickData):
    if body_title == 'body':
        word_count_data = article_wordcount_dict
    else:
        word_count_data = title_wordcount_dict

    curve_number = clickData['points'][0]['curveNumber']
    trace_name = checklist[curve_number]
    date = str(clickData['points'][0]['x'])
    bars = [go.Bar(
                x = list(word_count_data[trace_name][week_month][date].keys()),
                y = list(word_count_data[trace_name][week_month][date].values())
    )]
    return {
        'data': bars
    }

I am also getting this error when I am using a callback to use the clickData to get the trace name from the figure. I followed These Instructions

Traceback: AttributeError: 'Graph' object has no attribute 'figure'

right_graph_div = html.Div(id='output-div',children=[dcc.Graph(id='output-figure')],
                           style={'width': '75%', 'float': 'right', 'display': 'inline-block'})

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

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

app.layout = html.Div([
    html.Div([
        html.Div([
            left_button_div,
            right_graph_div
        ]),
        html.Div(id='test', style={'width': '100%', 'display': 'inline-block'})
    ])
])

@app.callback(
    Output(component_id='output-figure', component_property='figure'),
    [Input(component_id='xaxis-column', component_property='value'),
    Input('agg-func', 'value'),
    Input('color-by', 'value')]
)

def update_output_div(x_axis_col, agg_func, color_by):
    
    if agg_func.lower() == 'count':
        x_axis_col = x_axis_col.lower().replace(' ', '_')
        fig = make_creative_count_plot(df, count_by_dimension=x_axis_col, 
                                       color_by=color_by.lower().replace(" ", "_"))
        return fig
    
@app.callback(
    Output('test', 'children'),
    [Input('output-figure', 'clickData')])

def display_click_data(click_data):
    
    if click_data is not None:
        curve_number = click_data['points'][0]['curveNumber']
        x_value = click_data['points'][0]['x']

### THIS IS WHERE THE ERROR OCCURS
        trace_name = json.dumps(app.layout['output-figure'].figure['data']) 
        return curve_number, ' ', x_value, ' ', trace_name


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

The make_creative_count_plot() function builds a figure with fig = make_subplots(specs=[[{"secondary_y": True}]])

I add a variable number of traces depending on user input. This section:

curve_number = click_data['points'][0]['curveNumber']
 x_value = click_data['points'][0]['x']

Outputs the expected data from the clickData. I just can’t access the figure data from the first callback figure output.

The figure generates and dynamically changes as expected.

Right after posting this (and after a few hours of recapping basics in Dash), I realized I needed to pass in the current State. So this is how I resolved it:

    Output('test', 'children'),
    [Input('output-figure', 'clickData')],
#### ADDED HERE
    [State('output-figure', 'figure')])

def display_click_data(click_data, figure):
    
    if click_data is not None:
        curve_number = click_data['points'][0]['curveNumber']
        x_value = click_data['points'][0]['x']
        
        trace_name = figure['data'][curve_number]['name']
2 Likes