Hover showing wrong labels in barchart + scatter graph

Hi there,

I have an horizontal barchart with a scatter (line) over it and when I hover over, it shows the wrong data/label.

Looks like if the bars had a div from where they are placed down to the x axis so the longest ones go over the shorter ones, making it imposible to hover over the short ones.

This is the example I’ve been working on (file here and full code below)

E.g. Starting with your pointer on the right side of the Power Supplies bar and moving it to the left:

  1. First part it is correct, Power supplies (blue)
  2. until it reaches the point where it would cross the line chart Other, where it shows Other (orange).
  3. If you keep going slowly it shows Other (blue),
  4. then Power Supplies (orange) when it meets the point,
  5. then Cables (orange) until it “goes out” of the line chart
  6. then Other (blue).
    It’d make sense that is because the line is over the bar chart, but why 3 and 6? I can’t seem to get Cables (blue) hovered, whenever it is out of the points of the line chart, it shows Other instead of Cables.

Any ideas?

Thank you!

import dash
from dash.dependencies import Input, Output
import dash_core_components as dcc
import dash_html_components as html
import plotly.graph_objs as go
import pandas as pd 
indent preformatted text by 4 spaces

df = pd.read_csv("item2.txt", sep=',')
cols = df.columns
cols = cols.map(lambda x: x.replace(' ', '_') if isinstance(x, (str, unicode)) else x)
df.columns = cols

app = dash.Dash()
app.css.append_css({'external_url': 'https://codepen.io/chriddyp/pen/bWLwgP.css'})
app.config['suppress_callback_exceptions']=False

app.layout = html.Div(
    html.Div(
    children=[
        #First row
        html.Div(
        children=[
            # Location Dropdown
            html.Div(
            children=[
                html.Label('Location'),
                dcc.Dropdown(id='location', 
                    options=[{'label': i, 'value': i} for i in df.Location.unique()], 
                )
            ], className="one columns"),
            
             # Site Dropdown
            html.Div(
            children=[    
                html.Label('Site'),
                dcc.Dropdown(id='site', 
                    options=[{'label': i, 'value': i} for i in df.Site.unique()],
                )
            ], className="one columns"),
    
           # Items
            html.Div(
            children=[    
                html.Label('Items'),
                html.Div(id='items_container')
                    #df.shape[0])
            ], className="one columns"),

           # Quantity
            html.Div(
            children=[    
                html.Label('Quantity'),
                html.Div(id='quantity_container')
            ], className="one columns"),

            html.Div(
            children=[
                html.Label('Placeholder'),
                html.Div(id='placeholder')
            ], className="eight columns", style={'width':'100%'}),


        ]), 

        html.Div(
        children=[
            html.Div(
            children=[    
                # BarChart with stock by type
                html.Div(id='bar_current_stock_by_type_container',
                    children=[dcc.Graph(id='bar_stock_by_type', clickData={'points': []}
                    )] 
                ),
            ], className="four columns"),
        ]),    
        
        html.Div(
        children=[
            html.Div(
            children=[
                # Table with data
                html.Div(id='table-container')
            ], className="twelve columns"),
        ])     
        
    ])
)

def get_filtered_df(location, site, clickData):
    
    if location is None and site is None:
       dff = df
    elif site is None:
        dff = df[df.Location == location]
    elif location is None:
        dff = df[df.Site == site]    
    else:
        dff = df[(df.Location == location) & (df.Site == site)]
  
    print (clickData["points"])
    
    if clickData["points"] != []:
        dataselected = clickData["points"][0]["y"]
        print dataselected
        for item in dff.Stock_Type:        
            if item == dataselected:
                dff2 = dff[(dff.Stock_Type == dataselected)]               
    else:
        dff2=dff
    return dff2


def generate_table(df, max_rows=10):
    return html.Table(
        [html.Tr([html.Th(col) for col in df.columns])] +
        [html.Tr([html.Td(df.iloc[i][col]) for col in df.columns]) 
        for i in range(min(len(df), max_rows))], id='full_table'
    )


def generate_bar_stock_by_type(df, byStockType):

    x = byStockType.sum()['Inventory_Value']/1000
    line = go.Scatter(
        x=x,    
        y=x.index,    
        name='Inventory value (k)'
    )

    x = byStockType.count()['Current_Balance']
    bar = go.Bar(
        x=x,          
        y=x.index,     
        orientation='h',
        name='Num Stock Units'
    )

    graph_stock = dcc.Graph(
        id='bar_stock_by_type',
        figure={'data': [bar,line]},  
        clickData={'points': [], 'range': None},
        config={'displayModeBar': True},
        style={}
    )
    return graph_stock


# Items Metric
@app.callback(
    dash.dependencies.Output('items_container', 'children'),
    [dash.dependencies.Input('location', 'value'),
     dash.dependencies.Input('site', 'value'), 
     dash.dependencies.Input('bar_stock_by_type', 'clickData')
     ])
def display_metric_items(location, site, clickData):
    dff = get_filtered_df(location, site, clickData)
    
    return html.H3(dff.shape[0])


# Quantity Metric
@app.callback(
    dash.dependencies.Output('quantity_container', 'children'),
    [dash.dependencies.Input('location', 'value'),
    dash.dependencies.Input('site', 'value'),
    dash.dependencies.Input('bar_stock_by_type', 'clickData')
    ])
def display_metric_quantity(location, site, clickData):
    dff = get_filtered_df(location, site, clickData)
    
    return html.H3(dff['Current_Balance'].sum())



# Table
@app.callback(
    dash.dependencies.Output('table-container', 'children'),
    [dash.dependencies.Input('location', 'value'),
    dash.dependencies.Input('site', 'value'), 
    dash.dependencies.Input('bar_stock_by_type', 'clickData')
    ])
def display_table(location, site, clickData):
    dff = get_filtered_df(location, site, clickData)

    return generate_table(dff)


# Horizontal Bar Chart
@app.callback(
    dash.dependencies.Output('bar_current_stock_by_type_container', 'children'),
    [dash.dependencies.Input('location', 'value'),
    dash.dependencies.Input('site', 'value')])
def display_bar_current_stock_by_type(location, site):
    dff = get_filtered_df(location, site, clickData={'points': [], 'range': None})
    byStockType = dff.groupby('Stock_Type')

    return generate_bar_stock_by_type(dff, byStockType)


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

@Ireneg555 would you mind sharing a URL to the graph? You can share this with a URL by clicking on the “Edit in Chart Studio” button. That’ll save the plotly.js engineers some time in reproducing the example. There is a quick tutorial here: 📣 Sharing Graphs with Chart Studio

Hi Chris,

Here’s the link to the graph https://plot.ly/~Ireneg555/4/

Thanks for the help and for this amazing tool!

1 Like

Good description here. Yeah, 3 and 6 do seem odd. Some of this idiosyncrasy might be due to the fact that everything is horizontal - it usually feels a little more intuitive when things are vertical. In any case, I’ll open an issue in plotly.js

You might try setting the hover type to closest instead of compare. This will always display a hover label over whatever element that you are closest too, instead of comparing values across x values. It’s the same effect as if you were to click on “Show closest data on hover” in the toolbar:
59%20PM
To set this programmatically, add:

figure={'data': [...], 'layout': {'hovermode': 'closest'}}
2 Likes

Tracking the issue in the plotly.js repo here: https://github.com/plotly/plotly.js/issues/2463. Thanks for reporting!

1 Like