Update second graph while hovering on first graph

I’m trying to implement the very same feature that hovering on one graph results in a separate plot as on Part 4. Interactive Graphing and Crossfiltering | Dash for Python Documentation | Plotly

However, as the example is rather extend I’m not sure whether it’s correct what I did and/or what is not.

So far, I have:

df2 = all_df.melt(id_vars=['Device','time', 'Path'], value_vars=['V+','V-','I_A', 'I_fil'])
df2['path'] = df2['Path']

external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']
app = dash.Dash(__name__, title='Dashboard', external_stylesheets=external_stylesheets)             

colors = {
        'background': '#000000',
        'text': '#f3ff00'
        }

app.layout = html.Div(
                    children = [
    
                            html.Div([
                                    html.H4('Dauertest'),
                                    html.Div(children = ""),

                                    dcc.Graph(id = 'General' 
                                              , figure={}
                                              ),

                                    dcc.RadioItems(
                                                  id="signals",
                                                  options = ['V+', 'V-', 'I_A', 'I_fil'],
                                                  value = 'V+',
                                                  inline=True
                                                  ),
                                    
                                    dcc.Graph(id = 'Zoomed' 
                                              ),
                                    html.Br(),
                                    ]),
])


@app.callback(
              Output("General", "figure"), 
              Input("signals", "value")
              )
def update_scatter_chart(signals):
    df3 = df2.query('variable==@signals').groupby('path').first() 

    fig_general = px.scatter(df3
                              , x = "time"
                              , y = 'value'
                              , custom_data = ['Path']
                              , color = 'Device'
                              , symbol = 'variable'
                              , hover_name = "Device"
                              , opacity = 0.6
                              , template = 'plotly_dark'
                              , marginal_y = "rug"
                              )
    fig_general.update_layout(
                              transition_duration = 500
                              , autosize = True
                              , height = 700
                              , hovermode = 'closest'
                              )
    fig_general.update_traces(marker = dict(
                                            size = 14
                                            )
                              )
    return fig_general


def update_zoom_chart(df5):
    
    fig_zoom = px.scatter(df5
                              , x = "time"
                              , y = 'value'
                              , color = 'Device'
                              , symbol = 'variable'
                              , hover_name = "Device"
                              , opacity = 0.6
                              , template = 'plotly_dark'
                              )
    fig_zoom.update_layout(
                              transition_duration = 500
                              , autosize = True
                              , height = 500
                              )

    return fig_zoom

@app.callback(
              Output("Zoomed", "figure"), 
              Input('General', 'hoverData')
              )
def update_hover(hoverData):
    path = hoverData['points'][0]['customdata']
    df5 = df2[df2['Path' == path]]
    return update_zoom_chart(df5)

At the moment I receive the error:

in update_hover
    path = hoverData['points'][0]['customdata']
TypeError: 'NoneType' object is not subscriptable

But according to the linked example I do not see the difference?

Can you provide dataframe too? And I think if you want to use customdata you should add hover_data in your fig.

Sure, yes, it can be found on Filebin | 2k1rbe9w0kh2au02
The Path in the data is actually a really long string which reflects the source of the single data sets/excel files.

I thought with custom_data I’m addressing which data I’m going to use for updating the graph via hovering? Let’s say, I thought this is mandatory?

btw, meanwhile I figured out that

path = hoverData['points'][0]['customdata'][0]

creates a “better” error but yeah, it is still one :slight_smile:

ValueError: Value of 'custom_data_0' is not the name of a column in 'data_frame'. Expected one of ['Device', 'time', 'Path', 'variable', 'value'] but received: A1.xls

It doesn’t look very off but yet I don’t know how to handle this one.

I think first of all you should change from customdata to hovertext because as I said if you want to use customdata you will have to set hover_data in fig_general. Second: You are returning Device name as hover_name, so I think you need to change from df5 = df2[df2['Path' == path]] to df5=df2[df2['Device']==path]. If you want to use Path, I think you need to change hover_name. Full code as below:

import dash
from dash import dcc
from dash import html
import dash_bootstrap_components as dbc
from dash.dependencies import Input, Output
import pandas as pd
import plotly.express as px
from dash.exceptions import PreventUpdate
all_df = pd.read_csv('data.csv')

df2 = all_df.melt(id_vars=['Device','time', 'Path'], value_vars=['V+','V-','I_A', 'I_fil'])
df2['path'] = df2['Path']

external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']
app = dash.Dash(__name__, title='Dashboard', external_stylesheets=external_stylesheets)             

colors = {
        'background': '#000000',
        'text': '#f3ff00'
        }

app.layout = html.Div(
                    children = [
    
                            html.Div([
                                    html.H4('Dauertest'),
                                    html.Div(children = ""),

                                    dcc.Graph(id = 'General' 
                                              , figure={}
                                              ),

                                    dcc.RadioItems(
                                                  id="signals",
                                                  options = ['V+', 'V-', 'I_A', 'I_fil'],
                                                  value = 'V+',
                                                  inline=True
                                                  ),
                                    
                                    dcc.Graph(id = 'Zoomed' 
                                              ),
                                    html.Br(),
                                    ]),
])


@app.callback(
              Output("General", "figure"), 
              Input("signals", "value")
              )
def update_scatter_chart(signals):
    df3 = df2.query('variable==@signals').groupby('path').first() 
    fig_general = px.scatter(df3
                              , x = "time"
                              , y = 'value'
                              , custom_data = ['Path']
                              , color = 'Device'
                              , symbol = 'variable'
                              , hover_name = "Device"
                              , opacity = 0.6
                              , template = 'plotly_dark'
                              , marginal_y = "rug"
                              )
    fig_general.update_layout(
                              transition_duration = 500
                              , autosize = True
                              , height = 700
                              , hovermode = 'closest'
                              )
    fig_general.update_traces(marker = dict(
                                            size = 14
                                            )
                              )
    return fig_general


def update_zoom_chart(df5):
    
    fig_zoom = px.scatter(df5
                              , x = "time"
                              , y = 'value'
                              , color = 'Device'
                              , symbol = 'variable'
                              , hover_name = "Device"
                              , opacity = 0.6
                              , template = 'plotly_dark'
                              )
    fig_zoom.update_layout(
                              transition_duration = 500
                              , autosize = True
                              , height = 500
                              )

    return fig_zoom

@app.callback(
              Output("Zoomed", "figure"), 
              Input('General', 'hoverData')
              )
def update_hover(hoverData):
    if hoverData:
        path = hoverData['points'][0]['hovertext']
        df5 = df2[df2['Device']== path]
        return update_zoom_chart(df5)
    else:
        raise PreventUpdate

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

So if you want to use Path to update second graph, you can use below code:

import dash
from dash import dcc
from dash import html
import dash_bootstrap_components as dbc
from dash.dependencies import Input, Output
import pandas as pd
import plotly.express as px
from dash.exceptions import PreventUpdate
all_df = pd.read_csv('data.csv')

df2 = all_df.melt(id_vars=['Device','time', 'Path'], value_vars=['V+','V-','I_A', 'I_fil'])
df2['path'] = df2['Path']

external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']
app = dash.Dash(__name__, title='Dashboard', external_stylesheets=external_stylesheets)             

colors = {
        'background': '#000000',
        'text': '#f3ff00'
        }

app.layout = html.Div(
                    children = [
    
                            html.Div([
                                    html.H4('Dauertest'),
                                    html.Div(children = ""),

                                    dcc.Graph(id = 'General' 
                                              , figure={}
                                              ),

                                    dcc.RadioItems(
                                                  id="signals",
                                                  options = ['V+', 'V-', 'I_A', 'I_fil'],
                                                  value = 'V+',
                                                  inline=True
                                                  ),
                                    
                                    dcc.Graph(id = 'Zoomed' 
                                              ),
                                    html.Br(),
                                    ]),
])


@app.callback(
              Output("General", "figure"), 
              Input("signals", "value")
              )
def update_scatter_chart(signals):
    df3 = df2.query('variable==@signals').groupby('path').first() 
    fig_general = px.scatter(df3
                              , x = "time"
                              , y = 'value'
                              , custom_data = ['Path']
                              , color = 'Device'
                              , symbol = 'variable'
                              , hover_name = "Path"
                              , opacity = 0.6
                              , template = 'plotly_dark'
                              , marginal_y = "rug"
                              )
    fig_general.update_layout(
                              transition_duration = 500
                              , autosize = True
                              , height = 700
                              , hovermode = 'closest'
                              )
    fig_general.update_traces(marker = dict(
                                            size = 14
                                            )
                              )
    return fig_general


def update_zoom_chart(df5):
    
    fig_zoom = px.scatter(df5
                              , x = "time"
                              , y = 'value'
                              , color = 'Device'
                              , symbol = 'variable'
                              , hover_name = "Device"
                              , opacity = 0.6
                              , template = 'plotly_dark'
                              )
    fig_zoom.update_layout(
                              transition_duration = 500
                              , autosize = True
                              , height = 500
                              )

    return fig_zoom

@app.callback(
              Output("Zoomed", "figure"), 
              Input('General', 'hoverData')
              )
def update_hover(hoverData):
    if hoverData:
        path = hoverData['points'][0]['hovertext']
        df5 = df2[df2['Path']== path]
        return update_zoom_chart(df5)
    else:
        raise PreventUpdate

if __name__ == "__main__":
    app.run_server(debug=False, port=8054)

3 Likes

Awesome, thank you very, very, very much!
May I ask what exactly custom_data and hover_name are doing? Apparently, I misunderstood this.

However, is there a way to make the to updated graph show data already before hovering or clicking on a certain point? At the moment, when the dash board pops up, the graph is blank white until I hover over some data.
I guess I have to define:

    dcc.Graph(id = 'Zoomed' 
                         , hoverData = {'points': [{'customdata': df2['path'][0]}]}
                      ),

but this here is not the way to go.
Probably because I don’t understand how it is working, yet. Will see whether I can find comprehensive sources.

1 Like

I think you could update your callback as below:

@app.callback(
    Output("Zoomed", "figure"),
    Input('General', 'hoverData'))
def update_hover(hoverData):
    if hoverData:
        path = hoverData['points'][0]['hovertext']
        df5 = df2[df2['Path']== path]
        return update_zoom_chart(df5)
    else:
        
        df5 = df2[df2['Path']== df2['Path'].iloc[0]]
        return update_zoom_chart(df5)
1 Like

You’re the best, thank you!