I would like to set the color of a Bokeh line plot (Bokeh version 0.12.5) using a ColumnDataSource. However, with a line plot nothing is plotted. On the other hand, if I use a circle renderer everything works as expected. Below is an example program with both a line plot and a circle plot and you can comment/uncomment the appropriate lines to see the plotting behavior. I also included a line of code for a line plot where the color is explicitly defined and the plot works perfectly. I have seen a couple similar questions asked but could not find a solid solution to this problem or determine if I am just doing something fundamentally wrong. Thanks for your help.
# bokeh version 0.12.5
# run in terminal with: python -m bokeh serve --show line_plot_color.py
from bokeh.io import curdoc
from bokeh.models import ColumnDataSource
from bokeh.plotting import Figure
from bokeh.layouts import row
source = ColumnDataSource(data = dict(color = ['green','green','green'], xs = [1,2,3], ys = [1,2,3]))
fig = Figure(plot_width=300, plot_height=300)
#r = fig.circle('xs','ys',source = source, size = 12, fill_color = 'color') # works as expected
r = fig.line('xs','ys',source = source, line_color = 'color') # fails to plot; no errors or warnings in terminal
#r = fig.line('xs','ys',source = source, line_color = 'green') # works as expected
layout = row(fig)
curdoc().add_root(layout)
First to help you with debugging the bokeh server, it's very useful to use the devtools that come with web browsers. The devtools' console will contain useful debugging information as is the case for your example.
Second, looking through the docs the line glyph method is not set up to receive a column data source value for its coloring. If you want to plot multiple lines with different colors on a single figure, then you can use the multi_line glyph. To use this glyph, you need to modify your data source xs and ys to be a list of list for each line in your multi_line. Here's a quick example.
source2 = ColumnDataSource(data = dict(color = ['green','red'], xs = [[1, 2],[2, 4]], ys = [[1, 2],[2, 4]]))
r = fig.multi_line('xs','ys',source = source2, line_color = 'color')
Related
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.
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.
I tried to plot multiple line on bokeh server and it almost work find.
But there is one problem of colors for lines.
My code is like below and when I operate in on bokeh server, it raise 'Runtime error' , why? how can i fix it?
p.multi_line('year', 'area', alpha=0.6, color=BuGn8, source=source)
RuntimeError:
Supplying a user-defined data source AND iterable values to glyph methods is
not possibe. Either:
Pass all data directly as literals:
p.circe(x=a_list, y=an_array, ...)
Or, put all data in a ColumnDataSource and pass column names:
source = ColumnDataSource(data=dict(x=a_list, y=an_array))
p.circe(x='x', y='x', source=source, ...)
bokeh.pallettes.BuGn8 is not a color, it's a list of colors. And, as the error states, you can't mix lists and a data source when using glyph methods.
If you need a different color for each line, you can add another column to your data source and pass the name of the column to the color attribute of multi_line glyph function.
A small example:
from bokeh.io import output_file, show
from bokeh.models import ColumnDataSource
from bokeh.plotting import figure
from bokeh.palettes import BuGn8
p = figure()
ds = ColumnDataSource(data=dict(xs=[[1, 2, 3], [2, 3, 4]],
ys=[[3, 2, 1], [4, 3, 2]],
color=[BuGn8[0], BuGn8[-1]]))
p.multi_line(xs='xs', ys='ys', color='color', source=ds, line_width=10)
output_file("test.html")
show(p)
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.
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.