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

Help us help you! Writing your post to the following guidelines will help Bokeh Discourse reviewers understand your question, and increases the likelihood of a response.

What are you trying to do?

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.

Here is the code:

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)

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.

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

@maaxxwell1 this is an known, open issue:

Unfortunately fixing it will be non-trivial, incur some risk, and likely also require breaking API changes. Given that very few people have ever actually commented on the issue, there has not been much appetite amongst maintainers to prioritize this, relative to other issues.

You can certainly comment on the issue to add your experience, which may help move the needle. In the mean time the best workaround I can suggest is to use two different CDS for (i.e. one for the circles, and one for the hexes). The downside is that the data will be duplicated, but it will allow the inspection indices to be set independently.

Bryan, thanks for the swift response that’s very helpful. Using 2 CDS fixes the hovering problem indeed, however it creates another one downstream in my code. I would like to use box select to select any or all of the data points in either chart and display their characteristics in a table.

With 2 CDS I can only see the data for either the hex or circle in table - I can’t see both data sets at the same time and the graying out of the unselected data points doesn’t work properly. if you select the reds the blacks don’t gray out and vice and versa. This was working properly with one CDS. Is there a further workaround for this? Please see the extended code below:

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, column
from bokeh.models.tools import HoverTool
from bokeh.models.filters import GroupFilter
from bokeh.models.widgets import Panel, Tabs, DataTable, TableColumn
from bokeh.models import CustomJS

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)

#-----

    # table
f3 = ColumnDataSource()    
    
columns = [
    TableColumn(field="Make", title="Make"),
    TableColumn(field="Color", title="Color"),
    TableColumn(field="Price", title="Price"),
    TableColumn(field="Hp", title="Hp"),
    TableColumn(field="Score", title="Score"),
    TableColumn(field="Milage", title="Milage"),
    TableColumn(field="Origin", title="Origin"),
]

table = DataTable(
    source=f3,
    columns=columns,
    sortable=True,
    selectable=True,
    editable=True)

    # Reds
source.selected.js_on_change(
    "indices",
    CustomJS(
        args=dict(source_data=source, f3=f3, table=table),
        code="""
        var inds = cb_obj.indices;
        var d1 = source_data.data;
        var d2 = f3.data;
        
        d2['Make'] = []
        d2['Color'] = []
        d2['Price'] = []
        d2['Hp'] = []
        d2['Score'] = []
        d2['Milage'] = []
        d2['Origin'] = []
                
        for (var i = 0; i < inds.length; i++) {
            
            d2['Make'].push(d1['Make'][inds[i]])
            d2['Color'].push(d1['Color'][inds[i]])
            d2['Price'].push(d1['Price'][inds[i]])
            d2['Hp'].push(d1['Hp'][inds[i]])
            d2['Score'].push(d1['Score'][inds[i]])
            d2['Milage'].push(d1['Milage'][inds[i]])
            d2['Origin'].push(d1['Origin'][inds[i]])
            
        }
        f3.change.emit();
        table.change.emit();""", ), )

    # Blacks
source2.selected.js_on_change(
    "indices",
    CustomJS(
        args=dict(source_data=source2, f3=f3, table=table),
        code="""
        var inds = cb_obj.indices;
        var d1 = source_data.data;
        var d2 = f3.data;
        
        d2['Make'] = []
        d2['Color'] = []
        d2['Price'] = []
        d2['Hp'] = []
        d2['Score'] = []
        d2['Milage'] = []
        d2['Origin'] = []
                
        for (var i = 0; i < inds.length; i++) {
            
            d2['Make'].push(d1['Make'][inds[i]])
            d2['Color'].push(d1['Color'][inds[i]])
            d2['Price'].push(d1['Price'][inds[i]])
            d2['Hp'].push(d1['Hp'][inds[i]])
            d2['Score'].push(d1['Score'][inds[i]])
            d2['Milage'].push(d1['Milage'][inds[i]])
            d2['Origin'].push(d1['Origin'][inds[i]])
            
        }
        f3.change.emit();
        table.change.emit();""", ), )
        
#-----

charts = row(children=[f1, f2])
tab = Panel(child=column(charts, table), title='Cars')
tabs = Tabs(tabs=[ tab])
show(tabs)

I’m afraid I don’t have specific suggestions offhand. You may be advised to look for a different tool other than Bokeh at present.

Thanks for your help I have left a comment on the original thread as suggested.

1 Like