Bokeh hovertool in multiple_line plot - python

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.

Related

How to add a fixed-position tooltip in Bokeh?

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:

Bokeh: disable Auto-ranging while using Edit Tools

I've included the PolyDrawTool in my Bokeh plot to let users circle points. When a user draws a line near the edge of the plot the tool expands the axes which often messes up the shape. Is there a way to freeze the axes while a user is drawing on the plot?
I'm using bokeh 1.3.4
MRE:
import numpy as np
import pandas as pd
import string
from bokeh.io import show
from bokeh.plotting import figure
from bokeh.models import ColumnDataSource, LabelSet
from bokeh.models import PolyDrawTool, MultiLine
def prepare_plot():
embedding_df = pd.DataFrame(np.random.random((100, 2)), columns=['x', 'y'])
embedding_df['word'] = embedding_df.apply(lambda x: ''.join(np.random.choice(list(string.ascii_lowercase), (8,))), axis=1)
# Plot preparation configuration Data source
source = ColumnDataSource(ColumnDataSource.from_df(embedding_df))
labels = LabelSet(x="x", y="y", text="word", y_offset=-10,x_offset = 5,
text_font_size="10pt", text_color="#555555",
source=source, text_align='center')
plot = figure(plot_width=1000, plot_height=500, active_scroll="wheel_zoom",
tools='pan, box_select, wheel_zoom, save, reset')
# Configure free-hand draw
draw_source = ColumnDataSource(data={'xs': [], 'ys': [], 'color': []})
renderer = plot.multi_line('xs', 'ys', line_width=5, alpha=0.4, color='color', source=draw_source)
renderer.selection_glyph = MultiLine(line_color='color', line_width=5, line_alpha=0.8)
draw_tool = PolyDrawTool(renderers=[renderer], empty_value='red')
plot.add_tools(draw_tool)
# Add the data and labels to plot
plot.circle("x", "y", size=0, source=source, line_color="black", fill_alpha=0.8)
plot.add_layout(labels)
return plot
if __name__ == '__main__':
plot = prepare_plot()
show(plot)
The PolyDrawTool actually updates a ColumnDataSource to drive a glyph that draws what the users indicates. The behavior you are seeing is a natural consequence of that fact, combined with Bokeh's default auto-ranging DataRange1d (which by default also consider every glyph when computing the auto-bounds). So, you have two options:
Don't use DataRange1d at all, e.g. you can provide fixed axis bounds when you call figure:
p = figure(..., x_range=(0,10), y_range=(-20, 20)
or you can set them after the fact:
p.x_range = Range1d(0, 10)
p.y_range = Range1d(-20, 20)
Of course, with this approach you will no longer get any auto-ranging at all; you will need to set the axis ranges to exactly the start/end that you want.
Make DataRange1d be more selective by explicitly setting its renderers property:
r = p.circle(...)
p.x_range.renderers = [r]
p.y_range.renderers = [r]
Now the DataRange models will only consider the circle renderer when computing the auto-ranged start/end.

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)

Multiple HoverTools for different lines (bokeh)

I have more than one line on a bokeh plot, and I want the HoverTool to show the value for each line, but using the method from a previous stackoverflow answer isn't working:
https://stackoverflow.com/a/27549243/3087409
Here's the relevant code snippet from that answer:
fig = bp.figure(tools="reset,hover")
s1 = fig.scatter(x=x,y=y1,color='#0000ff',size=10,legend='sine')
s1.select(dict(type=HoverTool)).tooltips = {"x":"$x", "y":"$y"}
s2 = fig.scatter(x=x,y=y2,color='#ff0000',size=10,legend='cosine')
fig.select(dict(type=HoverTool)).tooltips = {"x":"$x", "y":"$y"}
And here's my code:
from bokeh.models import HoverTool
from bokeh.plotting import figure
source = ColumnDataSource(data=dict(
x = [list of datetimes]
wind = [some list]
coal = [some other list]
)
)
hover = HoverTool(mode = "vline")
plot = figure(tools=[hover], toolbar_location=None,
x_axis_type='datetime')
plot.line('x', 'wind')
plot.select(dict(type=HoverTool)).tooltips = {"y":"#wind"}
plot.line('x', 'coal')
plot.select(dict(type=HoverTool)).tooltips = {"y":"#coal"}
As far as I can tell, it's equivalent to the code in the answer I linked to, but when I hover over the figure, both hover tools boxes show the same value, that of the wind.
You need to add renderers for each plot. Check this. Also do not use samey label for both values change the names.
from bokeh.models import HoverTool
from bokeh.plotting import figure
source = ColumnDataSource(data=df)
plot = figure(x_axis_type='datetime',plot_width=800, plot_height=300)
plot1 =plot.line(x='x',y= 'wind',source=source,color='blue')
plot.add_tools(HoverTool(renderers=[plot1], tooltips=[('wind',"#wind")],mode='vline'))
plot2 = plot.line(x='x',y= 'coal',source=source,color='red')
plot.add_tools(HoverTool(renderers=[plot2], tooltips=[("coal","#coal")],mode='vline'))
show(plot)
The output look like this.

Change Color of Non-selected Bokeh Lines

I am starting to use Bokeh to plot data that does not share a common x or y variable. I would like to be able to select a line and have the other, non-selected lines, grey out. Ideally the selected line would also be brought to the front of the plot.
So far I been able to get the line selected, but I can't find a way of "greying out" the non-selected lines, or setting the level of the selected line.
import numpy as np
from bokeh.plotting import figure, show, output_file
from bokeh.models.sources import ColumnDataSource
from bokeh.models import Line,TapTool
output_file("test.html")
x0s = np.random.randint(0,20,20)
y0s = np.random.randint(0,20,20)
x1s = np.random.randint(0,20,20)
y1s = np.random.randint(0,20,20)
p_left = figure(tools=[TapTool()])
for xs,ys in zip([x0s,x1s],[y0s,y1s]):
source = ColumnDataSource({'x': xs, 'y': ys})
default_line = Line(x='x', y='y', line_color='blue', line_width=2)
selected_line = Line(line_color='red', line_width=4)
nonselected_line = Line(line_color='grey')
p_left.add_glyph(source,default_line,selection_glyph=selected_line,nonselection_glyph=nonselected_line)
show(p_left)
I'm in a similar situation, and found this example:
https://docs.bokeh.org/en/latest/docs/user_guide/styling.html#selected-and-unselected-glyphs
Haven't tried it out myself, but seems to be close to what you're looking for.
EDIT Just tried it, worked flawlessly for me.

Categories

Resources