I have multiple line plots to draw on a single figure and I am doing this using bokeh.plotting. Using
p0.line(),
p0.extra_y_ranges(),
and
p0.add_layout(LinearAxes())
p0 being 1 bokeh figure.
I would like to have range_sliders for each y axis separately on the right. Would this be possible using bokeh?
For the primary y axis, the range slider works fine using
# set up RangeSlider y_counts
range_slider_c = RangeSlider(
title="c",
start=-10,
end=400,
step=1,
value=(-1, 300),
height = 250,
orientation='vertical',
show_value= False,
direction = 'rtl'
)
range_slider_c.js_link("value", p0.y_range, "start", attr_selector=0)
range_slider_c.js_link("value", p0.y_range, "end", attr_selector=1)
But I am unclear about how to call the additional y axes's ranges like I did for p0.y_range in js.link.
I hope I have been able to explain my requirements properly.
This answers is based on the answer on your previous question.
As it is mentioned there, the extra y-ranges are saved in a dictionary with the keyword extra_y_ranges. Therefor you only have to change the js_link using extra_y_ranges with a valid name. For example range_slider_c.js_link("value", p0.extra_y_ranges["c", "start", attr_selector=0).
Complete minimal example
from bokeh.layouts import column
from bokeh.models import LinearAxis, Range1d, CustomJS, RangeSlider
from bokeh.plotting import figure, show, output_notebook
output_notebook()
data_x = [1,2,3,4,5]
data_y = [1,2,3,4,5]
color = ['red', 'green', 'magenta', 'black']
p = figure(plot_width=500, plot_height=300)
p.line(data_x, data_y, color='blue')
range_sliders = []
for i, c in enumerate(color, start=1):
name = f'extra_range_{i}'
lable = f'extra range {i}'
p.extra_y_ranges[name] = Range1d(start=0, end=10*i)
p.add_layout(LinearAxis(axis_label=lable, y_range_name=name), 'right')
p.line(data_x, data_y, color=c, y_range_name=name)
range_slider = RangeSlider(start=0, end=10*i, value=(1,9*i), step=1, title=f"Slider {lable}")
range_slider.js_link("value", p.extra_y_ranges[name] , "start", attr_selector=0)
range_slider.js_link("value", p.extra_y_ranges[name] , "end", attr_selector=1)
range_sliders.append(range_slider)
show(column(range_sliders+[p]))
Output
Comment
To stack the sliders and the figure i use the column layout, which takes a list of bokeh objects. Other layouts are available.
Related
I have plot
with secondary axis added like this:
plot.extra_x_ranges['sec_x_axis'] = Range1d(0, 100)
ax2 = LinearAxis(x_range_name="sec_x_axis", axis_label="secondary x-axis")
plot.add_layout(ax2, 'above')
x_axis is x_axis_type='datetime', so bokeh show ms on second x-axis too. This is not good.
Is there a way I can put my labels on this axis? I have a list of str labels like:
my_labels = ['21.5; 315.1', '21.6; 315.0', '21.7; 315.0', '21.7; 314.9',.....]
I found FuncTickFormatter but it takes JS code inside, so I can't handle it.
Maybe there is another way to do this?
To override the values of the labels use major_label_overrides on the appropriate axis. You can pass a dictionary like {1:'A', ...}, where 1 is the place to overwrite and A is the new label.
To avoid "wrong" labels while zooming, you can set the ticker direcetlly as list unsing ticker.
In your case the axis is p.above[0].
Comment
If you add a LinearAxis to a figure with an already existing DatetimeAxis, the new axis shoudn't be effected and therefor shouldn't be formatted as datetime. I used the latest version 2.4.3 and it works as expected. Use the minimal example to try it on your own.
Minimal Example
This code is based on the twin_axis.py example published by the authors of bokeh.
from numpy import arange, linspace, pi, sin
from bokeh.models import LinearAxis, Range1d
from bokeh.plotting import figure, show, output_notebook
output_notebook()
x = arange(-2*pi, 2*pi, 0.2)
x2 = arange(-pi, pi, 0.1)
y = sin(x)
y2 = sin(x2)
p = figure(
width=400,
height=400,
x_range=(-6.5, 6.5),
y_range=(-1.1, 1.1),
min_border=80,
x_axis_type="datetime"
)
p.circle(x, y, color="crimson", size=8)
p.yaxis.axis_label = "red circles"
p.yaxis.axis_label_text_color ="crimson"
p.extra_x_ranges['foo'] = Range1d(-pi, pi)
p.circle(x2, y2, color="navy", size=8, x_range_name="foo")
ax2 = LinearAxis(x_range_name="foo", axis_label="blue circles")
ax2.axis_label_text_color ="navy"
p.add_layout(ax2, 'above')
# set ticker to avoid wrong formatted labels while zooming
p.above[0].ticker = list(range(-3,4))
# overwrite labels
p.above[0].major_label_overrides = {key: item for key, item in zip(range(-3,4), list('ABCDEFG'))}
show(p)
default
overwritten labels
I currently have a gmap displaying gps points, however, I was hoping there was a way to colour code my GPS points based on which month they were recorded ? I have looked around online but am struggling to implement it into my own code. My dataset consists of GPS points collected throughout 2017, with a localDate index (in datetime format), and a longitude and latitude:
2017-11-12 |5.043978|118.715237
Bokeh and gmap code:
def plot(lat, lng, zoom=10, map_type='roadmap'):
gmap_options = GMapOptions(lat=lat, lng=lng,
map_type=map_type, zoom=zoom)
# the tools are defined below:
hover = HoverTool(
tooltips = [
# #price refers to the price column
# in the ColumnDataSource.
('Date', '#{Local Date}{%c}'),
('Lat', '#Lat'),
('Lon', '#Lon'),
],
formatters={'#{Local Date}': 'datetime'}
)
# below we replaced 'hover' (the default hover tool),
# by our custom hover tool
p = gmap(api_key, gmap_options, title='Malaysia',
width=bokeh_width, height=bokeh_height,
tools=[hover, 'reset', 'wheel_zoom', 'pan'])
source = ColumnDataSource(day2017Averageddf)
center = p.circle('Lon', 'Lat', size=4, alpha=0.5,
color='yellow', source=source)
show(p)
return p
p = plot(Lat, Lon, map_type='satellite')
The base idea is to pass the colors to the color keyword in p.circle(). You are using one color, but you could create also a list of colors with the correct length and implement your own logic or you could make use of a mapper.
The code below is a copy from the original documentation about mappers.
from bokeh.models import ColumnDataSource
from bokeh.palettes import Spectral6
from bokeh.plotting import figure, output_notebook, show
from bokeh.transform import linear_cmap
output_notebook()
x = [1,2,3,4,5,7,8,9,10]
y = [1,2,3,4,5,7,8,9,10]
#Use the field name of the column source
mapper = linear_cmap(field_name='y', palette=Spectral6 ,low=min(y) ,high=max(y))
source = ColumnDataSource(dict(x=x,y=y))
p = figure(width=300, height=300, title="Linear Color Map Based on Y")
p.circle(x='x', y='y', line_color=mapper,color=mapper, fill_alpha=1, size=12, source=source)
color_bar = ColorBar(color_mapper=mapper['transform'], width=8)
p.add_layout(color_bar, 'right')
show(p)
To come back to you problem. If the items in your Local Date column are of type pd.Timestamp, you can create a column "month" by this line
day2017Averageddf["month"] = day2017Averageddf["Local Date"].month
and use it for the mapper.
I only started to use Bokeh recently. I have a scatter plot in which I would like to color each marker according to a certain third property (say a quantity, while the x-axis is a date and the y-axis is a given value at that point in time).
Assuming my data is in a data frame, I managed to do this using a linear color map as follows:
min_q = df.quantity.min()
max_q = df.quantity.max()
mapper = linear_cmap(field_name='quantity', palette=palettes.Spectral6, low=min_q, high=max_q)
source = ColumnDataSource(data=get_data(df))
p = figure(x_axis_type="datetime")
p.scatter(x="date_column", y="value", marker="triangle", fill_color=mapper, line_color=None, source=source)
color_bar = ColorBar(color_mapper=mapper['transform'], width=8, location=(0,0))
p.add_layout(color_bar, 'right')
This seems to work as expected. Below is the plot I get upon starting the bokeh server.
Then I have a callback function update() triggered upon changing value in some widget (a select or a time picker).
def update():
# get new df (according to new date/select)
df = get_df()
# update min/max for colormap
min_q = df.quantity.min()
max_q = df.quantity.max()
# I think I should not create a new mapper but doing so I get closer
mapper = linear_cmap(field_name='quantity', palette=palettes.Spectral6 ,low=min_q, high=max_q)
color_bar.color_mapper=mapper['transform']
source.data = get_data(df)
# etc
This is the closest I could get. The color map is updated with new values, but it seems that the colors of the marker still follow the original pattern. See picture below (given that quantity I would expect green, but it is blue as it still seen as < 4000 as in the map of the first plot before the callback).
Should I just add a "color" column to the data frame? I feel there is an easier/more convenient way to do that.
EDIT: Here is a minimal working example using the answer by bigreddot:
from bokeh.io import curdoc
from bokeh.layouts import column
from bokeh.plotting import figure
from bokeh.models import Button, ColumnDataSource, ColorBar, HoverTool
from bokeh.palettes import Spectral6
from bokeh.transform import linear_cmap
import numpy as np
x = [1,2,3,4,5,7,8,9,10]
y = [1,2,3,4,5,7,8,9,10]
z = [1,2,3,4,5,7,8,9,10]
source = ColumnDataSource(dict(x=x, y=y, z=z))
#Use the field name of the column source
mapper = linear_cmap(field_name='z', palette=Spectral6 ,low=min(y) ,high=max(y))
p = figure(plot_width=300, plot_height=300, title="Linear Color Map Based on Y")
p.circle(x='x', y='y', line_color=mapper,color=mapper, fill_alpha=1, size=12, source=source)
color_bar = ColorBar(color_mapper=mapper['transform'], width=8, location=(0,0))
p.add_tools(HoverTool(tooltips="#z", show_arrow=False, point_policy='follow_mouse'))
p.add_layout(color_bar, 'right')
b = Button()
def update():
new_z = np.exp2(z)
mapper = linear_cmap(field_name='z', palette=Spectral6 ,low=min(new_z), high=max(new_z))
color_bar.color_mapper=mapper['transform']
source.data = dict(x=x, y=y, z=new_z)
b.on_click(update)
curdoc().add_root(column(b, p))
Upon update, the circles will be colored according to the original scale: everything bigger than 10 will be red. Instead, I would expect everything blue until the last 3 circle on tops that should be colored green yellow and red respectively.
It's possible that is a bug, feel free to open a GitHub issue.
That said, the above code does not represent best practices for Bokeh usage, which is: always make the smallest update possible. In this case, this means setting new property values on the existing color transform, rather than replacing the existing color transform.
Here is a complete working example (made with Bokeh 1.0.2) that demonstrates the glyph's colormapped colors updating in response to the data column changing:
from bokeh.io import curdoc
from bokeh.layouts import column
from bokeh.plotting import figure
from bokeh.models import Button, ColumnDataSource, ColorBar
from bokeh.palettes import Spectral6
from bokeh.transform import linear_cmap
x = [1,2,3,4,5,7,8,9,10]
y = [1,2,3,4,5,7,8,9,10]
z = [1,2,3,4,5,7,8,9,10]
#Use the field name of the column source
mapper = linear_cmap(field_name='z', palette=Spectral6 ,low=min(y) ,high=max(y))
source = ColumnDataSource(dict(x=x, y=y, z=z))
p = figure(plot_width=300, plot_height=300, title="Linear Color Map Based on Y")
p.circle(x='x', y='y', line_color=mapper,color=mapper, fill_alpha=1, size=12, source=source)
color_bar = ColorBar(color_mapper=mapper['transform'], width=8, location=(0,0))
p.add_layout(color_bar, 'right')
b = Button()
def update():
new_z = np.exp2(z)
# update the existing transform
mapper['transform'].low=min(new_z)
mapper['transform'].high=max(new_z)
source.data = dict(x=x, y=y, z=new_z)
b.on_click(update)
curdoc().add_root(column(b, p))
Here is the original plot:
And here is the update plot after clicking the button
I'm trying to make a scatter plot in Bokeh based on the simple example code posted here.
The following code produces a working demo for a line plot:
from bokeh.layouts import column
from bokeh.models import CustomJS, ColumnDataSource, Slider
from bokeh.plotting import Figure, show
# fetch and clear the document
from bokeh.io import curdoc
curdoc().clear()
x = [x*0.005 for x in range(0, 100)]
y = x
source = ColumnDataSource(data=dict(x=x, y=y))
plot = Figure(plot_width=400, plot_height=400)
plot.line(x='x', y='y', source=source)
def callback(source=source, window=None):
data = source.data
f = cb_obj.value
x, y = data['x'], data['y']
for i in range(len(x)):
y[i] = window.Math.pow(x[i], f)
source.trigger('change')
slider = Slider(start=0.1, end=4, value=1, step=.1, title="Start week",
callback=CustomJS.from_py_func(callback))
layout = column(slider, plot)
show(layout)
It looks like this:
In this demo, when you adjust the slider and press the 'reset' icon, the plot re-draws itself based on the updated formula for y=f(x).
However, I want to make a scatter plot that changes, not a line plot.
Problem:
When I simply change plot.line in above code to plot.circle, the plot renders okay but it is static - it does not change when you shift the slider and press 'reset'. No error messages that I can see.
I found the answer in the documentation.
The final line in callback should be source.change.emit() not source.trigger('change'). I do not know the difference between these two but the latter works with circle plots.
I.e.
def callback(source=source, window=None):
data = source.data
f = cb_obj.value
x, y = data['x'], data['y']
for i in range(len(x)):
y[i] = window.Math.pow(x[i], f)
source.change.emit()
With the following code,
from bokeh.plotting import figure, show, output_file
from bokeh.sampledata.iris import flowers
colormap = {'setosa': 'red', 'versicolor': 'green', 'virginica': 'blue'}
colors = [colormap[x] for x in flowers['species']]
p = figure(title = "Iris Morphology")
p.xaxis.axis_label = 'Petal Length'
p.yaxis.axis_label = 'Petal Width'
p.circle(flowers["petal_length"], flowers["petal_width"],
color=colors, fill_alpha=0.2, size=10)
output_file("iris.html", title="iris.py example")
show(p)
I can make a circle plot where I color the species:
But what I want to do is to color all the point based on range of
value in petal_length.
I tried this code but fail:
from bokeh.models import LinearColorMapper
exp_cmap = LinearColorMapper(palette='Viridis256', low = min(flowers["petal_length"]), high = max(flowers["petal_length"]))
p.circle(flowers["petal_length"], flowers["petal_width"],
fill_color = {'field' : flowers["petal_lengh"], 'transform' : exp_cmap})
output_file("iris.html", title="iris.py example")
show(p)
And also in the final desired plot, how can I put the color bar that
show the range of values and the assigned value. Something like this:
I'm using Python 2.7.13.
To answer your first part, there was a small typo (petal_lengh instead of petal_length) but more importantly, using the bokeh.ColumnDataSource will solve your problem (I tried to do it without CDS and only got column errors):
from bokeh.plotting import figure, show, output_file
from bokeh.sampledata.iris import flowers
from bokeh.models import LinearColorMapper
from bokeh.models import ColumnDataSource
p = figure(title = "Iris Morphology")
p.xaxis.axis_label = "Petal Length"
p.yaxis.axis_label = "Petal Width"
source = ColumnDataSource(flowers)
exp_cmap = LinearColorMapper(palette="Viridis256",
low = min(flowers["petal_length"]),
high = max(flowers["petal_length"]))
p.circle("petal_length", "petal_width", source=source, line_color=None,
fill_color={"field":"petal_length", "transform":exp_cmap})
# ANSWER SECOND PART - COLORBAR
# To display a color bar you'll need to import
# the `bokeh.models.ColorBar` class and pass it your mapper.
from bokeh.models import ColorBar
bar = ColorBar(color_mapper=exp_cmap, location=(0,0))
p.add_layout(bar, "left")
show(p)
See also: https://github.com/bokeh/bokeh/blob/master/examples/plotting/file/color_data_map.py
The colormapper transform refers to a column name and does not accept actual literal lists of data. So all the data needs to be in a Bokeh ColumDataSource and the plotting funcs all need to refer to the column names. Fortunately this is straightforward:
p.circle("petal_length", "petal_width", source=flowers, size=20,
fill_color = {'field': 'petal_length', 'transform': exp_cmap})
Directions for legends outside the plot area are documented here:
https://docs.bokeh.org/en/latest/docs/user_guide/styling.html#outside-the-plot-area