Search code examples
pythonbokeh

Bokeh linked scatter charts with CDSView source data - 'hover_color' only works on the last glyph in figure


I am linking two scatter charts that have the same underlying pandas dataframe as source with Bokeh.

I am using CDS View to slice the data. The issue is that the hover color (gold) which identifies data points across the two charts (linked brushing) only works for the last glyph in each figure i.e. black cars. The red dots don't turn gold when hovering only the black dots do - please see pictures.

I discovered that if I change the order in which I code the glyphs (i.e. first blacks then reds) then it is the red dots turn gold but not the blacks... I would like for all dots to turn gold on both charts when hovered on regardless of which glyph they belong to.

I have spent a lot of time trying to find a solution to this without success. I am relatively new to Bokeh and may have missed something obvious. Some help would be greatly appreciated.

import pandas as pd
import bokeh.plotting as bk
from bokeh.models.sources import ColumnDataSource, CDSView
from bokeh.io import show
from bokeh.layouts import row
from bokeh.models.tools import HoverTool
from bokeh.models.filters import GroupFilter

data = {'Make':  ['Tesla', 'Honda', 'VW', 'Audi' ],
        'Color': ['black', 'black', 'red', 'red' ],
        'Price': [12, 23, 54, 78 ],
        'Hp': [64, 28, 12, 11 ],
        'Score': [125, 128, 112, 111 ],
        'Milage': [64, 228, 212, 211 ],
        'Origin': ['USA', 'Japan', 'Germany', 'Germany' ]}

df = pd.DataFrame (data, columns = ['Make','Color','Price','Hp', 'Score', 'Milage', 'Origin'])
source = ColumnDataSource(df)
   
view_red = CDSView(source=source, filters=[GroupFilter(column_name='Color', group='red')])
view_black = CDSView(source=source, filters=[GroupFilter(column_name='Color', group='black')])

#-----

TOOLS = 'box_select, pan, box_zoom, reset'
f1 = bk.figure(plot_width=400, plot_height=300, tools=TOOLS)

reds = f1.circle('Score', 'Price', fill_color="red", fill_alpha=1.0,
                 hover_color='gold', legend_label="red", 
                 size=15, view=view_red, source=source)
    
blacks = f1.hex('Score', 'Price', fill_color="black", fill_alpha=1.0,
                  hover_color='gold', legend_label="black", 
                  size=15, view=view_black, source=source)

hover = HoverTool(mode = 'mouse')
hover.tooltips = [('Make', '@Make'), ('Price', '@Price'), ('Color', '@Color')]
f1.add_tools(hover)

#-----

f2 = bk.figure(plot_width=400, plot_height=300, tools=TOOLS)

reds = f2.circle('Hp', 'Milage', fill_color="red", fill_alpha=1.0,
                 hover_color='gold', legend_label="red", 
                 size=15, view=view_red, source=source)
    
blacks = f2.hex('Hp', 'Milage', fill_color="black", fill_alpha=1.0,
                  hover_color='gold', legend_label="black", 
                  size=15, view=view_black, source=source)

hover = HoverTool(mode = 'mouse')
hover.tooltips = [('Hp', '@Hp'), ('Origin', '@Origin'), ('Color', '@Color')]
f2.add_tools(hover)

#-----

charts = row(children=[f1, f2])
show(charts)

For completeness: this simplified example forms part of a larger script which has buttons to display the glyphs in each chart. Thanks

Reds dont turn gold

Blacks turn gold


Solution

  • Bryan from Bokeh informed that is a known issue and kindly provided a workaround, which is to use different CDS i.e. one for the circles, and one for the hexes. The downside is that the data will be duplicated, but it solves the issue outlined above.

    import pandas as pd
    import bokeh.plotting as bk
    from bokeh.models.sources import ColumnDataSource, CDSView
    from bokeh.io import show
    from bokeh.layouts import row
    from bokeh.models.tools import HoverTool
    from bokeh.models.filters import GroupFilter
    
    data = {'Make':  ['Tesla', 'Honda', 'VW', 'Audi' ],
            'Color': ['black', 'black', 'red', 'red' ],
            'Price': [12, 23, 54, 78 ],
            'Hp': [64, 28, 12, 11 ],
            'Score': [125, 128, 112, 111 ],
            'Milage': [64, 228, 212, 211 ],
            'Origin': ['USA', 'Japan', 'Germany', 'Germany' ]}
    
    df = pd.DataFrame (data, columns = ['Make','Color','Price','Hp', 'Score', 'Milage', 'Origin'])
    source = ColumnDataSource(df)
    source2 = ColumnDataSource(df)
       
    view_red = CDSView(source=source, filters=[GroupFilter(column_name='Color', group='red')])
    view_black = CDSView(source=source2, filters=[GroupFilter(column_name='Color', group='black')])
    
    #-----
    
    TOOLS = 'box_select, pan, box_zoom, reset'
    f1 = bk.figure(plot_width=400, plot_height=300, tools=TOOLS)
    
    reds = f1.circle('Score', 'Price', fill_color="red", fill_alpha=1.0,
                     hover_color='gold', legend_label="red", 
                     size=15, view=view_red, source=source)
        
    blacks = f1.hex('Score', 'Price', fill_color="black", fill_alpha=1.0,
                      hover_color='gold', legend_label="black", 
                      size=15, view=view_black, source=source2)
    
    hover = HoverTool(mode = 'mouse')
    hover.tooltips = [('Make', '@Make'), ('Price', '@Price'), ('Color', '@Color')]
    f1.add_tools(hover)
    
    #-----
    
    f2 = bk.figure(plot_width=400, plot_height=300, tools=TOOLS)
    
    reds = f2.circle('Hp', 'Milage', fill_color="red", fill_alpha=1.0,
                     hover_color='gold', legend_label="red", 
                     size=15, view=view_red, source=source)
        
    blacks = f2.hex('Hp', 'Milage', fill_color="black", fill_alpha=1.0,
                      hover_color='gold', legend_label="black", 
                      size=15, view=view_black, source=source2)
    
    hover = HoverTool(mode = 'mouse')
    hover.tooltips = [('Hp', '@Hp'), ('Origin', '@Origin'), ('Color', '@Color')]
    f2.add_tools(hover)
    
    #-----
    
    charts = row(children=[f1, f2])
    show(charts)
    

    More information on the issue can be found here:

    https://discourse.bokeh.org/t/bokeh-linked-scatter-charts-with-cdsview-source-data-hover-color-only-works-on-the-last-glyph-in-figure/7603