Deactivate hovertool for specific glyphs

Hello dear community,

would love to get some help on my issue:

I am trying to create a plot of geographical data in the field of Human Resources.
Therefore i created a map with multiple glyphs over a map.
Unfortunately my beloved hovertool shows tooltips for the patch in the background and for my circle render. See image below.
Is there a way to deactivate the hovertool for the patches but not for the circles?

Thanks alot for your help!

Here is my code. Please don’t destroy my bad coding. I am a total self taught rookie :slight_smile:


from bokeh.models import ColumnDataSource, Legend, LegendItem, DataTable, TableColumn, GeoJSONDataSource, StringFormatter
from bokeh.layouts import column, row
from bokeh.io import output_notebook, output_file
from bokeh.plotting import figure, show
from bokeh.transform import linear_cmap
from sql_strings import pjg
import pandas as pd
import json
from pyproj import Proj, transform



# Verbindung zum SQL-Server aufbauen---------------------------------------------------------------
import pyodbc
# Some other example server values are
# server = 'localhost\sqlexpress' # for a named instance
# server = 'myserver,port' # to specify an alternate port
server = 'km-vsv-rsdb2019\SQLEXPRESS'
database = 'Personalbewegung'
# username = 'myusername'
# password = 'mypassword'
# ENCRYPT defaults to yes starting in ODBC Driver 18. It's good to always specify ENCRYPT=yes on the client side to avoid MITM attacks.
cnxn = pyodbc.connect('DRIVER={SQL Server};SERVER=' + server+';DATABASE='+database+';Trusted_Connection=yes;')
cursor = cnxn.cursor()


# SQL-Abfrage durchführen, SQL-Strings immer im Modul 'sql_strings' ablegen-----------------------
df = pd.read_sql(pjg.pjg_wunschorte, cnxn)


# Koordinaten von gps in pseudo mercator konvertieren
# Neue Koordinaten zu Dataframe hinzufügen -------------------------------------------------------
df['merc_x'] = transform(Proj(init='epsg:4326'), Proj(init='epsg:3857'), df['GeoL'], df['GeoB'])[0]
df['merc_y'] = transform(Proj(init='epsg:4326'), Proj(init='epsg:3857'), df['GeoL'], df['GeoB'])[1]

main_source = ColumnDataSource(df)


# Tools für Karte festlegen-----------------------------------------------------------------------
TOOLS = "hover,pan,wheel_zoom,zoom_in,zoom_out,reset,tap,save,box_select,examine"
TOOLTIPS = [
    ('Schule', '@{Schule}'),
    ('Bewerber', '@Anzahl')
]


# Basiskarte erzeugen, figure setzen --------------------------------------------------------------
p = figure(x_range=(982552.128924, 1548919.941171), y_range=(6533837.177817, 6014187.692187),
           x_axis_type='mercator'   , y_axis_type="mercator",sizing_mode="scale_height", tools=TOOLS, tooltips=TOOLTIPS,
           title="Wunschorte", active_scroll='wheel_zoom')
p.add_tile("CartoDB Positron", retina=True)


# Verwaltungsgrenzen einbauen und rendern----------------------------------------------------------------------
#Bayern

linewidth_boarders = 2
with open('Verwaltungsgrenzen/bayernmercator.geojson', 'r') as bayern_geojson:
    data_bayern = json.loads(bayern_geojson.read())
geo_source_bayern = GeoJSONDataSource(geojson=json.dumps(data_bayern))
p.multi_line('xs', 'ys', source=geo_source_bayern, width=linewidth_boarders, alpha=0.2)

#Oberpfalz
with open('Verwaltungsgrenzen/oberpfalzmercator.geojson', 'r') as oberpfalz_geojson:
    data_oberpfalz = json.loads(oberpfalz_geojson.read())
geo_source_oberpfalz = GeoJSONDataSource(geojson=json.dumps(data_oberpfalz))
p.patches('xs', 'ys', source=geo_source_oberpfalz, width=linewidth_boarders, alpha=0.2, syncable = False)

#Niederbayern
with open('Verwaltungsgrenzen/niederbayernmercator.geojson', 'r') as niederbayern_geojson:
    data_niederbayern = json.loads(niederbayern_geojson.read())
geo_source_niederbayern = GeoJSONDataSource(geojson=json.dumps(data_niederbayern))
p.patches('xs', 'ys', source=geo_source_niederbayern, width=linewidth_boarders, alpha=0.2)

#Oberbayern
with open('Verwaltungsgrenzen/obmercator.geojson', 'r') as ob_geojson:
    data_ob = json.loads(ob_geojson.read())
geo_source_ob = GeoJSONDataSource(geojson=json.dumps(data_ob))
p.patches('xs', 'ys', source=geo_source_ob, width=linewidth_boarders, alpha=0.2)

#Oberfranken
with open('Verwaltungsgrenzen/ofmercator.geojson', 'r') as of_geojson:
    data_of = json.loads(of_geojson.read())
geo_source_of = GeoJSONDataSource(geojson=json.dumps(data_of))
p.patches('xs', 'ys', source=geo_source_of, width=linewidth_boarders, alpha=0.2)

#Mittelfranken
with open('Verwaltungsgrenzen/mfmercator.geojson', 'r') as mf_geojson:
    data_mf = json.loads(mf_geojson.read())
geo_source_mf = GeoJSONDataSource(geojson=json.dumps(data_mf))
p.patches('xs', 'ys', source=geo_source_mf, width=linewidth_boarders, alpha=0.2)

#Unterfranken
with open('Verwaltungsgrenzen/ufmercator.geojson', 'r') as uf_geojson:
    data_uf = json.loads(uf_geojson.read())
geo_source_uf = GeoJSONDataSource(geojson=json.dumps(data_uf))
p.patches('xs', 'ys', source=geo_source_uf, width=linewidth_boarders, alpha=0.2)

#Schwaben
with open('Verwaltungsgrenzen/swmercator.geojson', 'r') as sw_geojson:
    data_sw = json.loads(sw_geojson.read())
geo_source_sw = GeoJSONDataSource(geojson=json.dumps(data_sw))
p.patches('xs', 'ys', source=geo_source_sw, width=linewidth_boarders, alpha=0.2)


columns = [
    TableColumn(field='Schule',
                formatter=StringFormatter(font_style="bold")),
    TableColumn(field='Anzahl')
    
]
data_table = DataTable(source=main_source, columns=columns, width=400, height=1000)



# Circlegröße aus Anzahl berechnen-------------------------------------------------------------------
# r_meta = df['Anzahl'].values.tolist()
# r = [i * 4 for i in r_meta]
# df['circle_size'] = [i * 4 for i in r_meta]

# Circle-Farbe mit Anzahl scalieren lassen----------------------------------------------------------
cmap = linear_cmap(field_name='Anzahl', palette="Reds4", high=min(df['Anzahl']), low=4)

# Punkte rendern------------------------------------------------------------------------------------
r = p.scatter(x='merc_x', y='merc_y',source=main_source, fill_alpha=0.8, size=12, color=cmap, line_color='darkslategray')
p.scatter(x='merc_x', y='merc_y',source=main_source, fill_alpha=0.8, size=16, color=cmap, line_color='darkslategray')

# Legende setzen-------------------------------------------------------------------------------------
legend_index_selten = df.index[df['Anzahl']==min(df['Anzahl'])].tolist()[0]
legend_index_häufig = df.index[df['Anzahl']==max(df['Anzahl'])].tolist()[0]
legend = Legend(items=[
    LegendItem(label='selten', renderers = [r], index=legend_index_selten),
    LegendItem(label='häufig', renderers = [r], index=legend_index_häufig),
], title='Als Erstwunsch genannt')

container = row(p, height=1000)
layout = row(container, data_table)
# set separately to avoid also setting children
layout.sizing_mode = "stretch_both"

# Beschriftung zu Orten hinzufügen----------------------------------------------------------------
# labels1 = LabelSet(x='merc_x', y='merc_y', text='Schule',source=main_source , x_offset=5, y_offset=5)
# p.add_layout(labels1)
# Colorbar als Legende ---------------------------------------------------------------------------
# color_bar = ColorBar(color_mapper=cmap['transform'], width=10)
# p.add_layout(color_bar, 'right')


p.add_layout(legend)
show(layout)

So the issue is that you’re passing ‘hover’ as a tool to the figure when you instantiate it, which “auto” generates a HoverTool model and adds it to the figure. But you aren’t specifying what renderers you want the hovertool to “trigger” on, and the default behaviour is to trigger on ALL renderers (i.e. your scatter and patches). But you can’t specify the renderers for the HoverTool at that point in your code, because you haven’t made them yet! For this reason I recommend creating a HoverTool “manually” instead and pointing a variable to it so you have access to it “later” in your code once you have the renderers you want the HoverTool to trigger on.

Rough code-sketch below:

from bokeh.plotting import figure
from bokeh.models import HoverTool

TOOLTIPS = [
    ('Schule', '@{Schule}'),
    ('Bewerber', '@Anzahl')
]

#//doing this:
f = figure(tools=['hover'],tooltips=TOOLTIPS) 
#// is the same as doing this:
f = figure(tools=[])
hvr = HoverTool(tooltips=TOOLTIPS)
f.add_tools(hvr)

#// with either method, if you run 
dir(f.tools)
#//you'll see the HoverTool model attached to the figure
#//with the first method, you don't have an explicit variable pointing to the HoverTool 
#//but with the second method you do. This makes the second method a bit easier/intuitive if you need to put more specs on it later.   

#// now let's say you plot two different renderers
rscat = f.scatter(x=...)
pscat = f.patches(xs=...)
#// the figure's hovertool will by default, trigger for both of them
#// BUT we can modify this thanks to HoverTool having a renderers property, which takes a list of renderers
hvr.renderers = [rscat]

#now the hovertool will only trigger on the scatter and not the patches
2 Likes

Wow thank you a lot!

It works like a charm! And your answer was verry comprehensive!

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.