dcc.Graph with several go.Indicator bullet gauges not passing clickData to callback

I have some dcc.Graph elements that hold some traces of go.Indicator bullet gauges, and I want to access the clickData through a callback (that includes some customdata). For some reason the clickData just will not pass to the callback.

I tested it here with the callback at the bottom of this code and just printing the clickData to the id=‘test-text’ in the children element, but I can’t seem to get it to work. To save allot of code reading the function get_bars_container at the very top includes the dcc.Graph in question and the callback is in the very last lines of code at the bottom.

Any recommendations are welcome. Thanks in advance.


import dash
import pandas as pd
import dash_html_components as html
import dash_core_components as dcc
from dash.dependencies import Input, Output, State
import plotly.graph_objs as go
import dash_bootstrap_components as dbc
import numpy as np
from dash.exceptions import PreventUpdate
import scipy.stats as st
from app import app
import json
from Read_Data import df_team, df_norm_team_match


perc_format = "{:.0%}"
per90_format = "{:.1f}"
regular_format = "{:.0f}"


def get_match_name(df, TeamA, TeamB,MatchDate):
    return ' '.join([str(TeamA),'vs.',str(TeamB),str(MatchDate).split(' ')[0]])

def get_bars_container(bars_id, bars_title):
    bar_container = html.Div([dbc.Col([
        dbc.Row(
            [html.H5(children=bars_title.upper(), style={'font-weight': 'bold', 'display': 'inline-block', 'justifyContent': 'center', 'width': '65%',
                           'height': '100%'})
             ], justify="center", align="center", className="h-50"
        ),

        dbc.Row([dcc.Graph(id=bars_id, figure={'data': [{'x': [], 'y': []}], \
                                                             'layout': { 'hovermode': 'closest', \
                                                                        'paper_bgcolor': 'rgba(0,0,0,0)',
                                                                        'plot_bgcolor': 'rgba(0,0,0,0)'}}
                              )
                 ], justify="center", align="center", className="h-50"),
        ])



        ])
    return bar_container


team_match_dash = html.Div([
dbc.Row([
        dbc.Col(
            dbc.Card(
                dbc.CardBody(
                    [dbc.Row(
                        [html.H3(children='TEAM MATCH PERFORMANCE SUMMARY')
                         ], justify="center", align="center", className="h-50"
                    )

                        ,
                        dbc.Row(
                            [   html.Div(html.Img(id='team-title-logo-a', src='Al Duhail SC.png', style={'height': '80%'})),
                                dbc.Col(html.Div([
                                    dbc.Row(html.H5(id='title-text-team', children='', className="text-center"),
                                            justify="center", align="center", className="h-50")
                                ]
                                )
                                ),
                                html.Div(html.Img(id='team-title-logo-b', src='Al Duhail SC.png', style={'height': '80%'}))



                            ]
                        )]
                )

                , color='light'
            )
        )
    ]
    ),
html.Br(),
get_bars_container('overall-bars','Shot Summary'),
get_bars_container('passes-bars','Passing Summary'),
get_bars_container('linebreaks-bars','Linebreaks'),
get_bars_container('receptions-bars','Receptions'),
get_bars_container('offers-bars','Offers'),
get_bars_container('ball-progressions-bars','Ball Progressions'),
get_bars_container('crosses-bars','Crosses'),
get_bars_container('pressures-bars','Pressures'),
get_bars_container('duels-bars','Duels'),
get_bars_container('balls-won-bars','Balls Won'),
html.P(children = 'Test', id = 'test-text')
])

def get_value_vars(per90,value_vars_list):
    if per90 == 'per90':
        final_list = [i + '_per90' for i in value_vars_list]
    elif per90 == 'per100':
        final_list = [i + '_per100' for i in value_vars_list]
    else:
        final_list = value_vars_list
    return final_list

def get_melted_bar(df_match_data,value_vars, id_vars, per90, remove_string1, remove_string2,fieldZone, rename_value = 'value', incl_per_90 = True, secondary_melt = False, column_suffix = ''):

    if incl_per_90 == True:
        value_vars_final = get_value_vars(per90, value_vars)
    else:
        value_vars_final = value_vars

    value_vars_final = [i + column_suffix for i in value_vars_final]

    df_melted = pd.melt(df_match_data, id_vars=id_vars, value_vars=value_vars_final)
    df_melted = df_melted[df_melted['fieldZone'] == fieldZone]
    df_melted['varLabels'] = df_melted['variable'].apply(
        lambda x: x.replace('Physical Duels: Won', 'Physical').replace('Aerial Duels: Won', 'Aerial').replace(remove_string1, '')\
            .replace(remove_string2, '').replace('_per90', '').replace('_per100', '').replace('Offered ', '').replace('Received ', '')\
            .replace('_Clips', '').replace('|Successful', '').replace('_','-').capitalize())
    if secondary_melt == True:
        filter_cols = id_vars + ['value','varLabels']
        df_melted_final = df_melted[filter_cols].rename(columns={'value': rename_value})
    else:
        df_melted_final = df_melted

    return df_melted_final

def get_norm_dist(df, base_col, mean, std):
    return st.norm.cdf((base_col - mean) / std)

def add_image_marker(team, x, y):
    team_img_path  = ''.join(['Team Logos/',team,'.png'])
    image_marker = dict(
            source= app.get_asset_url(team_img_path),
            xref="x",
            yref="y",
            xanchor="center",
            yanchor="middle",
            x=x,
            y=y,
            sizex=0.2,
            sizey=0.2,
            opacity=0.8,
            sizing = "contain",
            layer="above"
        )
    return image_marker

def add_annotation_text(x,y, value, per90,varLabel):
    if '%' in varLabel:
        text_format = '{0:.0%}'
    elif per90 == 'per90':
        text_format = '{0:.1f}'
    else:
        text_format = '{0:.0f}'

    new_annotation = dict(xref='x domain',
                          yref='y domain',
                          x= x,
                          y=y,
                          xanchor = 'center',
                          yanchor = 'middle',
                          text = str(text_format.format(value)),
                          align = 'center',
                          font = {'size':20,
                                  'color':'black',
                                  'family': 'Droid Sans'},
                          showarrow=False)
    return new_annotation

def add_annotation_text_title(x,y, value, per90):

    new_annotation = dict(xref='x domain',
                          yref='y domain',
                          x= x,
                          y=y,
                          xanchor='center',
                          yanchor='middle',
                          text = value,
                          align = 'center',
                          font = {'size':20,
                                  'color':'#1C4E80',
                                  'family': 'Droid Sans'},
                          showarrow=False)
    return new_annotation

def get_bar_color(perc):
    if perc >0.67:
        color = 'green'
    elif perc < 0.33:
        color = 'red'
    else:
        color = 'yellow'
    return color

def update_team_image(game):
    if (game is None):
        raise PreventUpdate
    else:
        df_match_data = df_team[(df_team['MatchName'] == game)]
        teama = df_match_data.team_name_a.unique()[0]
        teamb = df_match_data.team_name_b.unique()[0]
        teama_img_path = ''.join(['Team Logos/',teama,'.png'])
        teama_img = app.get_asset_url(teama_img_path)
        teamb_img_path = ''.join(['Team Logos/', teamb, '.png'])
        teamb_img = app.get_asset_url(teamb_img_path)

    return teama_img, teamb_img


def get_bars(game,per90,fieldZone,value_vars,id_vars,remove_string_auto=True):
    if (game is None) | (per90 is None) | (fieldZone is None):
        raise PreventUpdate
    else:
        df_match_data = df_team[(df_team['MatchName'] == game)]
        teama = df_match_data.team_name_a.unique()[0]
        teamb = df_match_data.team_name_b.unique()[0]
        remove_string_loc = value_vars[0].find(':') + 2
        if remove_string_auto:
            remove_string1 = value_vars[0][0:remove_string_loc]
        else:
            remove_string1 = ''

        df_melted = get_melted_bar(df_match_data,value_vars, id_vars, per90, remove_string1,'', fieldZone)

        df_melted_clips = get_melted_bar(df_match_data, value_vars, id_vars, per90, remove_string1, '', fieldZone, 'clips', False, True, '_Clips')

        df_melted_mean =  get_melted_bar(df_norm_team_match, value_vars, ['fieldZone'], per90, remove_string1, '_mean', fieldZone, 'mean', True, True, '_mean')
        df_melted_std =  get_melted_bar(df_norm_team_match, value_vars, ['fieldZone'], per90, remove_string1, '_std', fieldZone, 'std', True, True, '_std')

        join_cols = id_vars + ['varLabels']
        df_melted_final = df_melted.merge(df_melted_clips, on = join_cols, how = 'left').merge(df_melted_mean, on = ['fieldZone','varLabels'], how = 'left')\
            .merge(df_melted_std, on = ['fieldZone','varLabels'], how = 'left')
        df_melted_final['percentile']= df_melted_final.apply(lambda x: get_norm_dist(x, x['value'],x['mean'],x['std']),axis=1)

        fig = go.Figure()
        annotation_list = []
        counter = 0
        for var in df_melted_final.varLabels.unique():
            total_count = len(df_melted_final.varLabels.unique())

            df_melted2 =  df_melted_final[( df_melted_final['varLabels']==var)]
            df_melted_team1 = df_melted2[df_melted2['team_name']==teamb]
            df_melted_team0 = df_melted2[df_melted2['team_name'] == teama]

            fig.add_trace(go.Indicator(
                mode = "gauge", value = df_melted_team0.value.iloc[0],
                align = 'left',
                domain = {'x': [0.1, 0.35], 'y': [counter/total_count, (counter+1)/total_count]},
                customdata=np.stack(
                    (df_melted_team0.clips, df_melted_team0.variable), axis=-1),
                gauge = {
                    'shape': "bullet",
                    'axis': {'range': [-1, 0],'visible': False},
                    'bgcolor': "white",
                    'steps': [
                    {'range': [ -(df_melted_team0.percentile.iloc[0]),0], 'color': get_bar_color(df_melted_team0.percentile.iloc[0])}],
                    'bar': {'color': 'rgba(0,0,0,0)'}}))

            fig.add_trace(go.Indicator(
                mode="gauge", value=df_melted_team0.value.iloc[0],
                align='right',
                domain={'x': [0.65, 0.9], 'y': [counter / total_count, (counter + 1) / total_count]},
                customdata=np.stack((df_melted_team1.clips, df_melted_team1.variable), axis=-1),
                gauge={
                    'shape': "bullet",
                    'axis': {'range': [0, 1],'visible': False},
                    'bgcolor': "white",
                    'steps': [
                        {'range': [0, df_melted_team1.percentile.iloc[0]],
                         'color': get_bar_color(df_melted_team1.percentile.iloc[0])}],
                    'bar': {'color': 'rgba(0,0,0,0)'}}))


            annotation_list.append(add_annotation_text(0.05,counter/total_count+0.5/total_count,df_melted_team0.value.iloc[0],per90,df_melted_team0.varLabels.iloc[0]))
            annotation_list.append(add_annotation_text_title(0.5,counter/total_count+0.5/total_count,df_melted_team1.varLabels.iloc[0],per90))

            annotation_list.append(add_annotation_text(0.95,counter/total_count+0.5/total_count,df_melted_team1.value.iloc[0],per90,df_melted_team1.varLabels.iloc[0]))
            counter += 1

        for annotation in annotation_list:
            fig.add_annotation(annotation)


        fig.update_layout(height=50*total_count, margin={'t': 0, 'b': 0, 'l': 0})
    return fig

@app.callback([Output('title-text-team', 'children'),
                Output('title-text-team-1', 'children'),
                Output('overall-bars','figure'),
                Output('passes-bars','figure'),
               Output('linebreaks-bars','figure'),
               Output('receptions-bars','figure'),
               Output('offers-bars','figure'),
               Output('ball-progressions-bars', 'figure'),
               Output('crosses-bars','figure'),
               Output('pressures-bars','figure'),
               Output('duels-bars','figure'),
               Output('balls-won-bars','figure'),
               Output('team-title-logo-a', 'src'),
               Output('team-title-logo-b', 'src'),
               Output('team-title-logo-a-1', 'src'),
               Output('team-title-logo-b-1', 'src')
               ],
              [Input('video-player', 'currentTime'),
               Input('submit-val', 'n_clicks')],
              [State('game-select', 'value'),
               State('per90-radio', 'value'),
               State('field-zone-radio', 'value'),
               State('last-clicks', 'children')])
def get_figures(vp,n_clicks,game,per90,fieldZone,last_clicks):
    if n_clicks > last_clicks:
        value_vars_overall = ['Shots: On Target','Shots: Total']
        value_vars_passing = [ 'Passes: Atk. Third','Passes: Mid. Third','Passes: Def. Third','Passes: Completion %','Passes: Total Passes']
        value_vars_receptions = ['Receptions: Received between 1_2','Receptions: Received between 2_3','Receptions: Received between 3_4', 'Receptions: In Behind']
        value_vars_offers = ['Offers: Offered in Front','Offers: Offered between 1_2','Offers: Offered between 2_3','Offers: Offered between 3_4','Offers: Offered In Behind']
        value_vars_linebreaks = [ 'Linebreaks: Defensive Line|Successful', 'Linebreaks: Midfield Line|Successful', 'Linebreaks: Front Line|Successful']
        value_vars_ball_progressions = ['Step Ins: Success%', 'Step Ins: Successful','Take Ons: Success%','Take Ons: Successful']
        value_vars_crosses = [ 'Crosses: Early','Crosses: Cutback','Crosses: Behind Defence', 'Crosses: In Front of Defence', 'Crosses: Successful','Crosses: Total']
        value_vars_pressures = ['Pressures: Pushing On','Pressures: Pressing', 'Pressures: Direct Pressure','Pressures: Indirect Pressure']
        value_vars_duels = ['Aerial Duels: Won', 'Physical Duels: Won']
        value_vars_balls_won = ['Balls Won: Block','Balls Won: Clearance','Balls Won: Interception',  'Balls Won: Tackle']


        id_vars = ['match_id', 'competition_name', 'matchYear', 'MatchName', 'team_name', 'fieldZone']

        overall_bars = get_bars(game,per90,fieldZone,value_vars_overall,id_vars)
        passing_bars = get_bars(game, per90, fieldZone, value_vars_passing, id_vars)
        linebreaks_bars = get_bars(game,per90,fieldZone,value_vars_linebreaks,id_vars)
        receptions_bars = get_bars(game, per90, fieldZone, value_vars_receptions, id_vars)
        offers_bars = get_bars(game, per90, fieldZone, value_vars_offers, id_vars)
        ball_progressions_bars = get_bars(game, per90, fieldZone, value_vars_ball_progressions, id_vars,False)
        crosses_bars = get_bars(game, per90, fieldZone, value_vars_crosses, id_vars)
        pressures_bars = get_bars(game, per90, fieldZone, value_vars_pressures, id_vars)
        duels_bars = get_bars(game, per90, fieldZone, value_vars_duels, id_vars)
        balls_won_bars = get_bars(game, per90, fieldZone, value_vars_balls_won, id_vars)
        teama_img, teamb_img = update_team_image(game)
        teama_img1, teamb_img1 = update_team_image(game)
        game1 = game
    else:
        raise PreventUpdate

    return game, game1, overall_bars, passing_bars, linebreaks_bars, receptions_bars,  offers_bars,ball_progressions_bars, crosses_bars,pressures_bars,duels_bars, balls_won_bars, teama_img, teamb_img,teama_img1, teamb_img1

@app.callback(Output('test-text', 'children'),
               [Input('overall-bars','clickData'),
                Input('linebreaks-bars','clickData')])
def test_click_data(cd,cd1):
    clickData = [p['prop_id'] for p in dash.callback_context.triggered][0]
    test = clickData['points'][0]['customdata'][0]
    df = pd.DataFrame(test, columns=['test_col'])
    return json.dumps(df.to_json())