How to add a fixed-position tooltip in Bokeh? - python

My goal is to add an HoverTool to my figure which displays the weekday by name. The date is defined by the x-axis values. I want to display this information at a fixed position even if the visible section is changed by a tool like BoxZoom.
Since the HoverTool needs at least one renderer I first tried to define a line but I did not find a way to define the position relative to the figure. In fact if I zoom it can happen, that this line is not in the visible part and the HoverTool isn't working anymore (or in an area which is not visible).
My second attempt was to define an extra_y_range to draw the line relative. But I did not find a way to unselect the BoxZoom for this axis.
import pandas as pd
from bokeh.plotting import figure, output_notebook, show
from bokeh.models import (
HoverTool,
LinearAxis,
Range1d,
)
output_notebook()
dr = pd.date_range('2020-01-01', '2020-01-05', freq='D')
p = figure(title="line", plot_width=300, plot_height=300, x_axis_type='datetime')
p.line(x=dr, y=[6, 7, 2, 4, 5])
p.extra_y_ranges.update({"extra": Range1d(0, 1)})
p.add_layout(LinearAxis(y_range_name="extra", axis_label=''), "right")
l = p.line(x=dr, y=0.8, color='gray', **{"y_range_name":"extra"})
p.add_tools(
HoverTool(
tooltips=[("", "#x{%A}")],
renderers=[l],
mode="vline",
formatters={"#x": "datetime"},
))
show(p)
Is there a way to add an HoverTool which stays at the same position in the visible area even if this area is effected by tools?

You can try this solution which works for Bokeh v2.1.1. In the code below the tooltip is fixed at absolute position on the screen. You can add yourself more generic solution independent of the plot position on the screen.
from bokeh.models import CustomJS
from bokeh.models import HoverTool
from bokeh.plotting import show, figure
import numpy as np
p = figure(plot_width = 300, plot_height = 300, tooltips = [('value X', '#x'), ('value Y', '#y')])
circles = p.circle(x=np.random.rand(10)*10, y=np.random.rand(10)*10, size=10)
callback = CustomJS(args={'p': p}, code="""
var tooltips = document.getElementsByClassName("bk-tooltip");
const tw = 100;
for (var i = 0; i < tooltips.length; i++) {
tooltips[i].style.top = '5px';
tooltips[i].style.left = p.width/2 - tw/2 + 'px';
tooltips[i].style.width = tw + 'px';
} """)
hover = p.select_one(HoverTool)
hover.renderers = [circles]
hover.callback = callback
hover.show_arrow = False
show(p)
Result:

Related

How to plot LabelSet outside the plot box?

I'm trying to highlight last value of a time series plot by plot its value on yaxis, as shown in this question. I prefer using LabelSet over Legend because you can precisely control the text positions and also using a data source to update it. But unfortunately, I can not find out how to draw label text outside the plot box.
Here is some code to plot LabelSet and notice how the text is only shown inside the box (66.1x is partially blocked by yaxis):
import pandas as pd
from bokeh.io import output_notebook
output_notebook()
from bokeh.plotting import figure, show
from bokeh.models import LabelSet, ColumnDataSource
#import bokeh.sampledata
#bokeh.sampledata.download()
from bokeh.sampledata.stocks import MSFT
df = pd.DataFrame(MSFT)[:50]
df["date"] = pd.to_datetime(df["date"])
p = figure(
x_axis_type="datetime", width=1000, toolbar_location='left',
title = "MSFT Candlestick", y_axis_location="right")
p.line(df.date, df.close)
ds = ColumnDataSource({'x': [df.date.iloc[-1]], 'y': [df.close.iloc[-1]], 'text': [' ' + str(df.close.iloc[-1])]})
ls = LabelSet(x='x', y='y', text='text', source=ds)
p.add_layout(ls)
show(p)
Please let me know how to show LabelSet outside the box, Thanks

Rect glyphs on a categorical axis behave unexpectedly when updating axis range via CustomJS - Bokeh

I am plotting a heatmap representation of a correlation matrix with Rect glyphs and a Labelset on a categorical axis. I'm trying to use a MultiSelect widget to dynamically update which elements are currently displayed.
Expected behavior - when I click on the button, the heatmap updates and displays only the elements that were picked in the widget (X and Y axis labels are identical). This works fine most of the time, but
Observed behavior: if I uncheck the first label in the list, the Rect glyphs shift right by 1 unit, while the LabelSet glyphs are displayed correctly.
Here's a minimal example:
from bokeh.io import show, output_notebook
from bokeh.models.callbacks import CustomJS
from bokeh.models.glyphs import Rect
from bokeh.events import ButtonClick
from bokeh.models.widgets import MultiSelect, Button
from bokeh.models import ColumnDataSource, LabelSet
from bokeh.layouts import row, column
from bokeh.plotting import figure
output_notebook()
data = pd.DataFrame({
'A': [0,1,2],
'B': [3,4,5],
'C': [6,7,8]
}, index=['A', 'B', 'C'])
data_transformed = data.stack().rename('value').reset_index()
source = ColumnDataSource(data_transformed)
p = figure(
width=200,
height=200,
x_range=data_transformed.level_0.unique().tolist(),
y_range=data_transformed.level_0.unique().tolist()[::-1],
x_axis_location="above"
)
rect = Rect(
x='level_0',
y='level_1',
width=1,
height=1,
fill_color='red',
line_color='black'
)
labels = LabelSet(
x='level_0',
y='level_1',
text='value',
level='annotation',
source=source
)
p.add_glyph(source, rect)
p.add_layout(labels)
multiselect = MultiSelect(
title='Pick the symbols',
value=data_transformed.level_0.unique().tolist(),
options=data_transformed.level_0.unique().tolist(),
height=200
)
button_callback = CustomJS(args=dict(plot=p, multiselect=multiselect), code="""
var x_range = multiselect.value.concat().sort()
var y_range = x_range.concat().reverse()
plot.x_range.factors = x_range
plot.y_range.factors = y_range
""")
button = Button(label="Calculate", button_type="primary")
button.js_on_event(ButtonClick, button_callback)
widgets = column(children=[multiselect, button])
layout = row(children=[widgets, p])
show(layout)
Initial display:
Image 1
After unchecking the last element and clicking the button (all good here):
Image 2
If the first element is unchecked:
Image 3
After switching to Circle glyphs it works as expected :
Image 4
Is this a bug or am I doing something wrong? Thanks.
I am using Bokeh 2.0.2
I'm pretty sure it's a bug. I've opened https://github.com/bokeh/bokeh/issues/10219.

Simple Date RangeSlider in Bokeh 2.0

I am trying to add a RangeSlider for dates to a graph in Bokeh 2.0.
Code looks like that (data comes from a dataframe in pandas):
p = figure(plot_width = 800, plot_height = 350, x_axis_type = "datetime")
p.line(df['date'], df['cases'], color='navy', alpha=0.5, legend_label = "cases", line_width = 2)
How do I add a slider to set and narrow the focus?
Thanks and cheers,
Ulrich
A couple of things to note:
It will produce the CDSView filters are not compatible with glyphs with connected topology suchs as Line and Patch warning. I leave it to you to decide whether it's something you care about
Changing the slider value will not alter the data range - I think, for the exact same reason, simply because using circle instead of line works just fine
import pandas as pd
from bokeh.io import show
from bokeh.layouts import column
from bokeh.models import ColumnDataSource, BooleanFilter, CDSView, DateRangeSlider, CustomJS
from bokeh.plotting import figure
df = pd.DataFrame(dict(date=['2020-01-01', '2020-01-02', '2020-01-03'], cases=[1, 2, 3]))
df['date'] = pd.to_datetime(df['date'])
p = figure(plot_width=800, plot_height=350, x_axis_type="datetime")
init_value = (df['date'].min(), df['date'].max())
slider = DateRangeSlider(start=init_value[0], end=init_value[1], value=init_value)
ds = ColumnDataSource(df)
date_filter = BooleanFilter(booleans=[True] * df.shape[0])
slider.js_on_change('value', CustomJS(args=dict(f=date_filter, ds=ds),
code="""\
const [start, end] = cb_obj.value;
f.booleans = Array.from(ds.data['date']).map(d => (d >= start && d <= end));
// Needed because of https://github.com/bokeh/bokeh/issues/7273
ds.change.emit();
"""))
p.circle('date', 'cases', source=ds, view=CDSView(source=ds, filters=[date_filter]),
color='navy', alpha=0.5, legend_label="cases", line_width=2)
show(column(p, slider))

Bokeh line 'disappearing' when using hover_line_alpha

I'm using the Bokeh package to plot a line chart.
I want a given line to bolden (alpha to increase) when I hover over it.
I added a hover tool and then added "hover_line_alpha = 0.6" in my line chart.
However when I hover over points on a given line, the line disappears altogether!
Can you help me fix this?
Code below so you can see my logic.
Thanks,
Ross
# Code in Question
from bokeh.io import output_notebook, show, output_file
from bokeh.plotting import figure
from bokeh.models import ColumnDataSource, HoverTool
output_notebook()
# set out axes
x = 'time_rnd'
y = 'count'
# set colour palette
col_brew = ['#8dd3c7','#ffffb3','#bebada','#fb8072','#80b1d3','#fdb462','#b3de69','#fccde5','#d9d9d9','#bc80bd','#ccebc5','#ffed6f']
# map out figure
plot = figure(tools='box_select, lasso_select, save' ,x_axis_type='datetime')
# add HoverTool
hover_info = [('time', '#hover_time'),
('word', '#word'),
('count', '#count')]
hover = HoverTool(names=['use'],tooltips=hover_info,
mode='mouse',
show_arrow=True
)
plot.add_tools(hover)
### FOR LOOP OF PLOT [THIS IS WHERE THE ISSUE MANIFESTS]
for i in top_wds_test:
df_eng_word = df_eng_timeline[df_eng_timeline['word']==i]
source = ColumnDataSource(df_eng_word)
plot.line(x, y, line_width = 3,
line_alpha = 0.1, line_color=col_brew[top_wds.index(i)],
hover_line_alpha = 0.6,
#hover_line_color = 'black',
#hover_line_color = col_brew[top_wds.index(i)],
source = source, legend=i, name = 'use'
)
plot.circle(x, y, fill_color='white', size=5,
selection_color='green',
nonselection_fill_color='grey',nonselection_fill_alpha=0.4,
hover_color='red',
source = source, name = 'use')
# add legend
plot.legend.location = "top_left"
plot.legend.label_text_font_style = 'bold'
# materialize the plot
show(plot)
There seems to be an issue when the renderers share a data source. However, this works (with Bokeh >= 0.13.0) if you let Bokeh create a new separate source for each glyph:
from bokeh.models import ColumnDataSource
from bokeh.plotting import figure, show
p = figure(tools="hover", tooltips="$name: #$name")
data=dict(x=[1,2,3], y1=[2,6,5], y2=[6,2,3])
p.line('x', 'y1', color="navy", line_width=3, source=data,
alpha=0.1, hover_color="navy", hover_alpha=0.6, name="y1")
p.line('x', 'y2',color="firebrick", line_width=3, source=data,
alpha=0.1, hover_color="firebrick", hover_alpha=0.6, name="y2")
show(p)

Bokeh hovertool in multiple_line plot

I'm new to bokeh and I just jumped right into using hovertool as that's why I wanted to use bokeh in the first place.
Now I'm plotting genes and what I want to achieve is multiple lines with the same y-coordinate and when you hover over a line you get the name and position of this gene.
I have tried to mimic this example, but for some reason the I can't even get it to show coordinates.
I'm sure that if someone who actually knows their way around bokeh looks at this code, the mistake will be apparent and I'd be very thankful if they showed it to me.
from bokeh.plotting import figure, HBox, output_file, show, VBox, ColumnDataSource
from bokeh.models import Range1d, HoverTool
from collections import OrderedDict
import random
ys = [10 for x in range(len(levelsdf2[(name, 'Start')]))]
xscale = zip(levelsdf2[('Log', 'Start')], levelsdf2[('Log', 'Stop')])
yscale = zip(ys,ys)
TOOLS="pan,wheel_zoom,box_zoom,reset,hover"
output_file("scatter.html")
hover_tips = levelsdf2.index.values
colors = ["#%06x" % random.randint(0,0xFFFFFF) for c in range(len(xscale))]
source = ColumnDataSource(
data=dict(
x=xscale,
y=yscale,
gene=hover_tips,
colors=colors,
)
)
p1 = figure(plot_width=1750, plot_height=950,y_range=[0, 15],tools=TOOLS)
p1.multi_line(xscale[1:10],yscale[1:10], alpha=1, source=source,line_width=10, line_color=colors[1:10])
hover = p1.select(dict(type=HoverTool))
hover.tooltips = [
("index", "$index"),
("(x,y)", "($x, $y)"),
]
show(p1)
the levelsdf2 is a pandas.DataFrame, if it matters.
I figured it out on my own. It turns out that version 0.8.2 of Bokeh doesn't allow hovertool for lines so I did the same thing using quads.
from bokeh.plotting import figure, HBox, output_file, show, VBox, ColumnDataSource
from bokeh.models import Range1d, HoverTool
from collections import OrderedDict
import random
xscale = zip(levelsdf2[('series1', 'Start')], levelsdf2[('series1', 'Stop')])
xscale2 = zip(levelsdf2[('series2', 'Start')], levelsdf2[('series2', 'Stop')])
yscale2 = zip([9.2 for x in range(len(levelsdf2[(name, 'Start')]))],[9.2 for x in range(len(levelsdf2[(name, 'Start')]))])
TOOLS="pan,wheel_zoom,box_zoom,reset,hover"
output_file("linesandquads.html")
hover_tips = levelsdf2.index.values
colors = ["#%06x" % random.randint(0,0xFFFFFF) for c in range(len(xscale))]
proc1 = 'Log'
proc2 = 'MazF2h'
expression1 = levelsdf2[(proc1, 'Level')]
expression2 = levelsdf2[(proc2, 'Level')]
source = ColumnDataSource(
data=dict(
start=[min(xscale[x]) for x in range(len(xscale))],
stop=[max(xscale[x]) for x in range(len(xscale))],
start2=[min(xscale2[x]) for x in range(len(xscale2))],
stop2=[max(xscale2[x]) for x in range(len(xscale2))],
gene=hover_tips,
colors=colors,
expression1=expression1,
expression2=expression2,
)
)
p1 = figure(plot_width=900, plot_height=500,y_range=[8,10.5],tools=TOOLS)
p1.quad(left="start", right="stop", top=[9.211 for x in range(len(xscale))],
bottom = [9.209 for x in range(len(xscale))], source=source, color="colors")
p1.multi_line(xscale2,yscale2, source=source, color="colors", line_width=20)
hover = p1.select(dict(type=HoverTool))
hover.tooltips = OrderedDict([
(proc1+" (start,stop, expression)", "(#start| #stop| #expression1)"),
("Gene","#gene"),
])
show(p1)
Works like a charm.
EDIT: Added a picture of the result, as requested and edited code to match the screenshot posted.
It's not the best solution as it turns out it's not all that easy to plot several series of quads on one plot. It's probably possible but as it didn't matter much in my use case I didn't investigate too vigorously.
As all genes are represented on all series at the same place I just added tooltips for all series to the quads and plotted the other series as multi_line plots on the same figure.
This means that if you hovered on the top line at 9.21 you'd get tooltips for the line at 9.2 as well, but If you hovered on the 9.2 line you wouldn't get a tooltip at all.

Categories

Resources