I've used Bokeh to generate a multiline chart that updates using a slider. I cannot find a way to have each line drawn with a different colour. I've tried using itertools to iterate through a palette, and passing a range of palette colours.
Here's the itertools approach (full_source is there to support the slider interaction which uses CustomJS):
import itertools
from bokeh.plotting import figure
from bokeh.embed import components
from bokeh.models import CustomJS, ColumnDataSource, Slider
from bokeh.palettes import Category20 as palette
from bokeh.models.glyphs import MultiLine
from bokeh.models.widgets import DataTable, TableColumn
from bokeh.layouts import column, row
from bokeh.io import show
data={'xdata':[[0, 1, 2, 4, 5, 6, 10, 11, 12], [4, 8, 16, 0, 13, 21, -3, 9, 21]],
'ydata':[[4, 8, 16, 0, 13, 21, -3, 9, 21], [0, 1, 2, 4, 5, 6, 10, 11, 12]]}
colors=itertools.cycle(palette[2])
source = ColumnDataSource(data)
full_source = ColumnDataSource(data)
glyph = MultiLine(xs='xdata', ys='ydata', line_color = next(colors))
p = figure(title = None, plot_width = 400, plot_height = 400, toolbar_location = None)
p.add_glyph(source, glyph)
print(glyph.line_color)
show(p)
This gives two lines, but both of the same colour. print(glyph.line_color) shows just one color passed - #1f77b4 (which is the first colour in the Category20 palette)
I've also tried using the example found here:
import itertools
from bokeh.plotting import figure
from bokeh.embed import components
from bokeh.models import CustomJS, ColumnDataSource, Slider
from bokeh.palettes import Spectral11
from bokeh.models.glyphs import MultiLine
from bokeh.models.widgets import DataTable, TableColumn
from bokeh.layouts import column, row
from bokeh.io import show
data={'xdata':[[0, 1, 2, 4, 5, 6, 10, 11, 12], [4, 8, 16, 0, 13, 21, -3, 9, 21]],
'ydata':[[4, 8, 16, 0, 13, 21, -3, 9, 21], [0, 1, 2, 4, 5, 6, 10, 11, 12]]}
my_pallet = Spectral11[0:2]
source = ColumnDataSource(data)
full_source = ColumnDataSource(data)
glyph = MultiLine(xs='xdata', ys='ydata', line_color = my_pallet)
p = figure(title = None, plot_width = 400, plot_height = 400, toolbar_location = None)
p.add_glyph(source, glyph)
print(glyph.line_color)
show(p)
This gives:
ValueError expected an element of either String, Dict(Enum('expr', 'field', 'value', 'transform'), Either(String, Instance(Transform), Instance(Expression), Color)) or Color, got ['#5e4fa2', '#3288bd', '#66c2a5']
How can I get multiple colours from a palette into a MultiLine graph?
Ok it looks like I'd not been using ColumnDataSource correctly. By passing the colours into the ColumnDataSource as an additional key:value pair in the data Dict, it works. I also could get rid of the MultiLine glyph object.
Working code is:
from bokeh.plotting import figure
from bokeh.embed import components
from bokeh.models import CustomJS, ColumnDataSource, Slider
from bokeh.palettes import Category20 as palette
from bokeh.models.widgets import DataTable, TableColumn
from bokeh.layouts import column, row
from bokeh.io import show
data = {'xs':[[...,...,..,][...,...,...]],'ys':[[...,...,..,][...,...,...]]}
length = len(data)
colors = palette[length]
#because Category20 has a minimum of 3 values, and length may be smaller
while len(colors)>length:
colors.pop()
data['color'] = colors
source = ColumnDataSource(data)
full_source = ColumnDataSource(data)
p = figure(title = None, plot_width = 400, plot_height = 400, toolbar_location = None)
p.multi_line(xs='xdata', ys='ydata', source=source, line_color='color')
Related
I have generated a bokeh 2d-histogram plot as mentioned in this StackOverflow answer. The code and respective image is given below. In the code, the count of data-points per bin is in the entries in H. How can I get access to an individual pixel-index to get the value at respective index in H and show it in the hover-text? I want to show count: 2 in the hover-text below x and y values, say. I tried using image_index mentioned here; probably new in Bokeh 3.0.1 but not sure how it works.
Note: It would be better to get an answer compatible with Bokeh 2.4.3 due to legacy code issues. Including holoviews tag as I know their outputs can be rendered as bokeh figure.
import numpy as np
from bokeh.plotting import figure, show
from bokeh.palettes import Turbo256
from bokeh.models import ColorBar, LinearColorMapper
a = np.array([1, 1.5, 2, 3, 4, 5])
b = np.array([15, 16, 20, 35, 45, 50])
H, xe, ye = np.histogram2d(a, b, bins=5)
data=dict(
image=[H],
x=[xe[0]],
y=[ye[0]],
dw=[xe[-1]-xe[0]],
dh=[ye[-1]-ye[0]]
)
TOOLTIPS = [
("x", "$x"),
("y", "$y"),
# ("image_index", "$image_index"),
]
p = figure(x_range=(min(xe),max(xe)), y_range=(min(ye),max(ye)), tools="pan,reset,hover", tooltips=TOOLTIPS)
color_map = LinearColorMapper(palette=Turbo256, low=1, high=H.max(), low_color='white')
color_bar = ColorBar(color_mapper=color_map, label_standoff=12)
p.image(source=data,image='image',x='x',y='y',dw='dw',dh='dh',color_mapper=color_map,)
p.add_layout(color_bar, 'right')
show(p)
I don't know the raw Bokeh code to do this, but in HoloViews it's:
import numpy as np, holoviews as hv
hv.extension('bokeh')
a = np.array([1, 1.5, 2, 3, 4, 5])
b = np.array([15, 16, 20, 35, 45, 50])
H, xe, ye = np.histogram2d(a, b, bins=5)
img = hv.Image(H[::-1], bounds=(-1,-1,1,1), vdims=['image_index'])
img.opts(tools=['hover'], width=500, cmap="Turbo", colorbar=True,
clim=(1,None), clipping_colors={'min': 'white'})
I was wondering if there was a way to color a line to follow the curve from the user specified input. Example is shown below. The user wants to color a line that starts from x = 11, to x = 14 (see image below for the result). I tried f.ex df.loc[..] where it tries to locate points closest to. But then it just colors it from x = 10 to 15. Anyone have an idea how to solve this? Do I need to add extra points between two points, how would I do that? The user might also add x = 11 to x = 19.
Appreciate any help or guidance.
from bokeh.plotting import figure, output_file, show
import pandas as pd
p = figure(width=600, height=600, tools="pan,reset,save")
data = {'x': [1, 2, 3, 6, 10, 15, 20, 22],
'y': [2, 3, 6, 8, 18, 24, 50, 77]}
df = pd.DataFrame(data)
p.line(df.x, df.y)
show(p)
What the result should look like when user inputs x = 11 (start) and x = 14 (end):
With pandas you can create an interpolated DataFrame from the original.
With this you can add a new line in red.
from bokeh.plotting import figure, output_notebook, show
import pandas as pd
output_notebook()
p = figure(width=600, height=600, tools="pan,reset,save")
data = {'x': [1, 2, 3, 6, 10, 15, 20, 22],
'y': [2, 3, 6, 8, 18, 24, 50, 77]}
df = pd.DataFrame(data)
df_interpolated = (df.copy()
.set_index('x')
.reindex(index = range(df['x'].min(), df['x'].max()))
.reset_index() # optional, you could write 'index' in the second line plot, too.
.interpolate()
)
p.line(df.x, df.y)
p.line(df_interpolated.x[11:14], df_interpolated.y[11:14], color="red")
show(p)
I am trying to set the absolute position of a Bokeh Chart inside a Layout so that one of the plots is shown on top of another plot. Right now when I am plotting something like this:
from bokeh.io import curdoc
from bokeh.plotting import figure
from bokeh.layouts import layout
import numpy as np
x = np.arange(1,10.1,0.1)
y = [i**2 for i in x]
categories = ['A', 'B']
values = [1000, 1500]
fig1 = figure(width=600,plot_height=600, title="First Plot")
fig1.line(x=x, y=y)
fig2 = figure(width=200,plot_height=250,x_range=categories,
title="Second Plot") fig2.vbar(x=categories, top=values, width=0.2)
l = layout([[fig1,fig2]])
curdoc().add_root(l)
The result will be this:
What I am searching for is some way to make it look like that:
How can this result be achieved?
Thank you!
This is what I came up with (works for Bokeh v1.0.4). You need to move your mouse over the plot to get the other one jump inside but you could also copy the JS code from the callback and manually add it to the HTML generated by Bokeh so you achieve the same result.
from bokeh.plotting import figure, show
from bokeh.layouts import Row
from bokeh.models import ColumnDataSource, CDSView, BooleanFilter, CustomJS, BoxSelectTool, HoverTool
import pandas as pd
plot = figure(tools = 'hover', tooltips = [("x", "#x"), ("y", "#y")])
circles = plot.circle('x', 'y', size = 20, source = ColumnDataSource({'x': [1, 2, 3], 'y':[1, 2, 3]}))
inner_plot = figure(name = 'inner_plot', plot_width = 200, plot_height = 200)
lines = inner_plot.line('x', 'y', source = ColumnDataSource({'x': [8, 9, 10], 'y':[8, 6, 8]}))
code = """ div = document.getElementsByClassName('bk-root')[0];
tooltip_plot = div.children[0].children[1]
tooltip_plot.style = "position:absolute; left: 340px; top: 350px;"; """
callback = CustomJS(code = code)
plot.js_on_event('mousemove', callback)
show(Row(plot, inner_plot))
Result:
I'd like to use multiple Hovertools in one plot together with Hovertool's names attribute to selectivly apply each tool. Take for instance
hover1 = HoverTool(tooltips=[("group", "1")], names = ['line1'])
hover2 = HoverTool(tooltips=[("group", "2")], names = ['lines2'])
and the two data sources:
source1 = ColumnDataSource(data=dict(
xs=[[1, 3, 2], [3, 4, 6, 6]],
ys=[[2, 1, 4], [4, 7, 8, 5]],
))
source2 = ColumnDataSource(data=dict(
xs=[[1, 3, 2], [6, 7, 9, 8]],
ys=[[-1, 0, 1], [1, 1, 2, 1]]
))
I'd though the following (using the bokeh.models API) should do what I want
p = figure(plot_width=400, plot_height=400)
l1 = MultiLine(xs='xs', ys='ys', name='lines1')
l2 = MultiLine(xs='xs', ys='ys', name='lines2')
p.add_tools(hover)
p.add_tools(hover2)
p.add_glyph(source1, l1)
p.add_glyph(source2, l2)
show(p)
Alas the Hovertools in the resulting plot do not work (i.e. no tooltips are shown). Using the bokeh.plotting API as follows and everything works as expected:
p = figure(plot_width=400, plot_height=400, tools=[hover, hover2])
p.multi_line(xs='xs', ys='ys', source=source1, name='lines1')
p.multi_line(xs='xs', ys='ys', source=source2, name='lines2')
show(p)
Question: How does one replicate the result of the bokeh.plotting API with the bokeh.models API?
The names attribute of the HoverTool model in the Bokeh Documentation:
names: property type: List ( String )
A list of names to query for. If set, only renderers that have a matching value for their name attribute will be used.
With this
l1 = MultiLine(xs='xs', ys='ys', name='lines1')
You are assigning the name to the Multiline object and that is a glyph, not a renderer. So try this instead
from bokeh.io import output_notebook, show
output_notebook()
import numpy as np
from bokeh.plotting import figure
from bokeh.models.sources import ColumnDataSource
from bokeh.models.glyphs import MultiLine
from bokeh.layouts import row
from bokeh.models.tools import HoverTool
source = ColumnDataSource(data=dict(
xs1=[[1, 2, 3], [5, 6, 7]],
ys1=[[1, 2, 3], [6, 5, 7]],
xs2=[[7, 8, 9], [1, 2, 3]],
ys2=[[4, 5, 7], [6, 7, 2]],
)
)
hover1 = HoverTool(tooltips=[("group", "1")], names = ['lines1'])
hover2 = HoverTool(tooltips=[("group", "2")], names = ['lines2'])
p = figure(plot_width=400, plot_height=400)
l1 = MultiLine(xs='xs1', ys='ys1')
l2 = MultiLine(xs='xs2', ys='ys2')
r1 = p.add_glyph(source, l1, name='lines1') # the name is assigned to the renderer
r2 = p.add_glyph(source, l2, name='lines2')
# r1.name = 'lines1' # or you could assign the name like this as well
# r2.name = 'lines2'
p.add_tools(hover1)
p.add_tools(hover2)
# p = figure(plot_width=400, plot_height=400, tools=[hover1, hover2])
# p.multi_line(xs='xs1', ys='ys1', source=source, name='lines1')
# p.multi_line(xs='xs2', ys='ys2', source=source, name='lines2')
show(p)
You can easily remove Bokeh logo from a single figure doing the following:
from bokeh.plotting import figure, show
from bokeh.models.tools import PanTool, SaveTool
p = figure()
p.line([1, 2, 3, 4],[1, 4, 3, 0])
p.toolbar.logo = None
p.tools = [SaveTool(), PanTool()]
show(p)
or just using p.toolbar_location = None
I, however, didn't manage to hide it when having multiple figures:
from bokeh.plotting import figure, show
from bokeh.models.tools import PanTool, SaveTool
from bokeh.layouts import gridplot
from bokeh.models import ColumnDataSource, BoxZoomTool, WheelZoomTool, LassoSelectTool, BoxSelectTool, ResetTool, \
PanTool, TapTool, SaveTool
tools = [PanTool(), BoxZoomTool(match_aspect=True), WheelZoomTool(), BoxSelectTool(),
ResetTool(), TapTool(), SaveTool()]
figures = [figure(plot_width=800, plot_height=800,
tools=tools, output_backend="webgl", match_aspect=True) for i in range(2)]
figures[0].line([1, 2, 3, 4], [1, 4, 3, 0])
figures[0].toolbar.logo = None
figures[1].line([1, 2, 3, 4], [1, 4, 3, 0])
figures[1].toolbar.logo = None
show(gridplot([figures], merge_tools=True, sizing_mode='scale_height'))
I've also tried figures.toolbar.logo = None but of course it doesn't work as it's a list and it has no such attribute. How can i do that?
You can configured toolbar options to gridplot by passing a toolbar_options argument to gridplot:
grid = gridplot([figures], merge_tools=True, sizing_mode='scale_height',
toolbar_options=dict(logo=None))
show(grid)
This is documented in the Reference Guide entry for gridplot
logo=None didn't work for me. This css declaration did however:
<style>
.bk-logo {
display:none !important;
}
</style>