hover_tooltip_advanced#

import numpy as np

from bokeh.models import BoxAnnotation, CustomJS, HoverTool, Styles
from bokeh.models.dom import HTML, Index, ValueRef
from bokeh.palettes import Spectral11
from bokeh.plotting import figure, show

N = 1000
x = np.random.random(size=N) * 100
y = np.random.random(size=N) * 100
radii = np.random.random(size=N) * 1.5
colors = np.random.choice(Spectral11, size=N)

p = figure(
    title="Demonstrates hover tool with advanced and regular tooltip\nformatting and filtering side-by-side",
    tools="pan,wheel_zoom,box_select,crosshair",
)

p.circle(x, y, radius=radii, fill_color=colors, fill_alpha=0.6, line_color=None)

x_filter = CustomJS(code="""export default (args, tool, {value: x}) => 20 <= x && x <= 80""")
y_filter = CustomJS(code="""export default (args, tool, {value: y}) => 20 <= y && y <= 80""")

x_ref = ValueRef(style=dict(background_color="cyan"), field="x", filter=x_filter)
y_ref = ValueRef(style=dict(background_color="lime"), field="y", filter=y_filter)

def span(name: str, color: str):
    return f"""<span style="background-color: {color};">{name}</span>"""

html = HTML(
    style=Styles(
        display="grid",
        grid_template_columns="auto auto",
        column_gap="10px",
    ),
    html=[
        """<div>index:</div><div style="font-weight: bold;">#""", Index(), "</div>",
        f"<div>({span('x', 'cyan')}, {span('y', 'lime')}):</div><div>(", x_ref, ", ", y_ref, ")</div>",
        "<div>radius:</div>", ValueRef(field="radius", format="%.2f", formatter="printf"),
    ],
)

hover_advanced = HoverTool(
    description="Advanced hover",
    tooltips=html,
    attachment="left",
)
p.add_tools(hover_advanced)

hover_regular = HoverTool(
    description="Regular hover",
    tooltips=[
        ("index", "$index"),
        ("(x,y)", "(@x, @y)"),
        ("radius", "@radius{%.2f}"),
    ],
    formatters={
        "@radius": "printf",
    },
    attachment="right",
    filters={
        "@x": x_filter,
        "@y": y_filter,
    },
)
p.add_tools(hover_regular)

p.add_layout(BoxAnnotation(left=20, right=80, top=20, bottom=80, level="underlay"))

show(p)