How do I set default / active tools for a bokeh gridplot? - python

If one wants to define the active (default) tools for a bokeh plot, it can be set by passing "active_drag", "active_inspect", ... parameters to the figure instance as documented here.
I have not yet succeeded to set the standard-active tools for a gridplot, in which all single plots share a toolbar. This the relevant part of my code:
tools = [
PanTool(),
BoxZoomTool(),
WheelZoomTool(),
UndoTool(),
RedoTool(),
ResetTool(),
SaveTool(),
HoverTool(tooltips=[
("Value", "$y")
])
]
x_axes_range = Range1d(self.data.index[0], self.data.index[-1])
for plot_type, plot_settings in pcfg.plot_types[self.name].items():
plots.append(figure(x_axis_type="datetime", title=plot_type, plot_height = 400, x_range = x_axes_range,
tools = tools, active_drag = None, active_inspect = None, active_scroll = None, active_tap = None))
...
plots[plot_counter].line(self.data.index, self.data[parameter], color=parameter_settings[1], legend=parameter_settings[0])
...
gp = gridplot(plots, ncols = 1, sizing_mode = "scale_width")
script, div = components(gp)
So what happens is that the "BoxZoomTool()" is selected as active tool on the website I display it on, although I set the active tools to None in the figure initializations, but the available tools are those I passed to the figure() inits.
I did see the "toolbar_option" here, but I do not see how I can change the active tools using that parameter.

Update 6/4/2020
Well, it seems that somebody created a GitHub Issue and the changes were already merged. Let's see if this is working in the next Bokeh Version
Old Answer
Research
I believe you only can change the logo with toolbar_options like this:
toolbar_options=dict(logo='gray')
I hope there will more options in the future.
I have checked how to achieve what you want (I needed to do it as well) and it seems that the gridplot uses an special toolbar to join all the plot toolbars together: a ProxyToolbar
# Make the grid
tools = []
rows = []
for row in children:
row_tools = []
row_children = []
for item in row:
if merge_tools:
if item is not None:
for plot in item.select(dict(type=Plot)):
row_tools = row_tools + plot.toolbar.tools
plot.toolbar_location = None
if item is None:
width, height = 0, 0
for neighbor in row:
if isinstance(neighbor, Plot):
width = neighbor.plot_width
height = neighbor.plot_height
break
item = Spacer(width=width, height=height)
if isinstance(item, LayoutDOM):
item.sizing_mode = sizing_mode
if isinstance(item, Plot):
if plot_width:
item.plot_width = plot_width
if plot_height:
item.plot_height = plot_height
row_children.append(item)
else:
raise ValueError("Only LayoutDOM items can be inserted into Grid")
tools = tools + row_tools
rows.append(Row(children=row_children, sizing_mode=sizing_mode))
grid = Column(children=rows, sizing_mode=sizing_mode)
if not merge_tools:
return grid
if toolbar_location:
proxy = ProxyToolbar(tools=tools, **toolbar_options)
toolbar = ToolbarBox(toolbar=proxy, toolbar_location=toolbar_location)
The tools are gathered in a list to assign them to the special toolbar. I don´t see that the default active elements are collected anywhere.
Alternative solution
So what you could do is to create a toolbar and the gridplot manually, where you can set the attributes you want to the Toolbar class. Check this example I have built:
from bokeh.models import Button, ColumnDataSource, Range1d, Toolbar, ToolbarBox
from bokeh.models.tools import HoverTool, WheelZoomTool, PanTool, CrosshairTool
from bokeh.layouts import layout
from bokeh.plotting import curdoc, figure
x_range = Range1d(start=0, end=10)
y_range = Range1d(start=0, end=10)
# ------------------- PLOT 1 --------------------------- #
plot_1 = figure(
title='First figure',
width=400,
height=400,
x_range=x_range,
y_range=y_range,
toolbar_location=None,
x_axis_label='x axis',
y_axis_label='y axis',
)
x = [1, 2, 3, 4]
y = [4, 3, 2, 1]
source = ColumnDataSource(data=dict(x=x, y=y))
plot_1.circle(
x='x',
y='y',
source=source,
radius=0.5,
fill_alpha=0.6,
fill_color='green',
line_color='black',
)
# ------------------- PLOT 2 --------------------------- #
plot_2 = figure(
name='plot_2',
title='Second figure',
width=400,
height=400,
x_range=x_range,
y_range=y_range,
toolbar_location=None,
x_axis_label='x axis',
y_axis_label='y axis',
)
plot_2.circle(
x='x',
y='y',
source=source,
radius=0.5,
fill_alpha=0.6,
fill_color='red',
line_color='black',
)
# ---------------- ADD TOOLS TO THE PLOT --------------------- #
wheel_zoom = WheelZoomTool()
pan_tool = PanTool()
hover = HoverTool()
crosshair = CrosshairTool()
tools = (wheel_zoom, pan_tool, hover, crosshair)
toolbar = Toolbar(
tools=[wheel_zoom, pan_tool, hover, crosshair],
active_inspect=[crosshair],
# active_drag = # here you can assign the defaults
# active_scroll = # wheel_zoom sometimes is not working if it is set here
# active_tap
)
toolbar_box = ToolbarBox(
toolbar=toolbar,
toolbar_location='left'
)
plot_1.add_tools(*tools)
plot_2.add_tools(*tools)
# ----------------- PLOT LAYOUT -------------------------- #
layout_1 = layout(
children=[
[toolbar_box, plot_1, plot_2],
],
sizing_mode='fixed',
)
curdoc().add_root(layout_1)
Note: I am doing some tests and sometimes it is not working well. The tools are marked as default but randomly don´t work, I am afraid it has something to do with the JavaScript and asyncronous tasks. So maybe we should wait.
Second Alternative Solution
I think I found a solution that always work. This is a kind of workaround. In my example I use two plots, but only the toolbar of the first plot is shown. Anyway you need to set the default toolbar values to both plots.
from bokeh.models import Button, ColumnDataSource, Range1d, Toolbar, ToolbarBox
from bokeh.models.tools import HoverTool, WheelZoomTool, PanTool, CrosshairTool, LassoSelectTool
from bokeh.layouts import layout
from bokeh.plotting import curdoc, figure
x_range = Range1d(start=0, end=10)
y_range = Range1d(start=0, end=10)
# ------------------- PLOT 1 --------------------------- #
plot_1 = figure(
title='First figure',
width=400,
height=400,
x_range=x_range,
y_range=y_range,
toolbar_location='left', # show only the toolbar of the first plot
tools='',
x_axis_label='x axis',
y_axis_label='y axis',
)
x = [1, 2, 3, 4]
y = [4, 3, 2, 1]
source = ColumnDataSource(data=dict(x=x, y=y))
plot_1.circle(
x='x',
y='y',
source=source,
radius=0.5,
fill_alpha=0.6,
fill_color='green',
line_color='black',
)
# ------------------- PLOT 2 --------------------------- #
plot_2 = figure(
name='plot_2',
title='Second figure',
width=400,
height=400,
x_range=x_range,
y_range=y_range,
toolbar_location=None,
tools='',
x_axis_label='x axis',
y_axis_label='y axis',
)
plot_2.circle(
x='x',
y='y',
source=source,
radius=0.5,
fill_alpha=0.6,
fill_color='red',
line_color='black',
)
# ---------------- ADD TOOLS TO THE PLOT --------------------- #
wheel_zoom = WheelZoomTool()
lasso_select = LassoSelectTool()
pan_tool = PanTool()
hover = HoverTool()
crosshair = CrosshairTool()
tools = (wheel_zoom, lasso_select, pan_tool, hover, crosshair)
plot_1.add_tools(*tools)
plot_2.add_tools(*tools)
plot_1.toolbar.active_inspect=[crosshair] # defaults added to the first plot
plot_1.toolbar.active_scroll=wheel_zoom
plot_1.toolbar.active_tap=None
plot_1.toolbar.active_drag=lasso_select
plot_2.toolbar.active_inspect=[crosshair] # defaults added to the second plot
plot_2.toolbar.active_scroll=wheel_zoom
plot_2.toolbar.active_tap=None
plot_2.toolbar.active_drag=lasso_select
# ----------------- PLOT LAYOUT -------------------------- #
layout_1 = layout(
children=[
[plot_1, plot_2],
],
sizing_mode='fixed',
)
curdoc().add_root(layout_1)
Developers Feedback
In fact, Bryan (bokeh developer) told me on the chat
I was going to actually answer that default tool activation and grid plots had never been considered together and was not supported yet, tho if you have found something that works for your specific use case that's probably the best possible. As you say it's not generally the case that users should have to work with toolbars directly, they are finicky for a number of reasons.

Related

Bokeh colorbar customisation

Good day,
I have the following colorbar for my Bokeh chart.
My code for the colorbar is as follows:
palette = brewer['Greys'][8]
palette = palette[::-1]
formatter = PrintfTickFormatter(format='%1.0f')
color_mapper = LinearColorMapper(palette = palette, low = 0, nan_color = '#d9d9d9')
color_bar = ColorBar(color_mapper=color_mapper, label_standoff=8,width = 500, height = 20,
border_line_color=None,location = (0,0), orientation = 'horizontal', formatter = formatter)
I would like to make a couple of changes and was wondering if anyone can assist me with any of the following:
Add a border to the colorbar so that one can see where "0" starts,
Get the tick marks to line up with the colors,
Have the final value of the colorbar included (at the very end of the bar there needs to be an ending value,
Change the format of the ticks from e.g. 200000 to 200k.
Thank you to anyone who can assist me!
Have a great day.
Here is a collection:
ColorBar has a bar_line_color. Default is None. Set this to your favorite color (like "black")
ColorBar has a ticker and you can set this to BasicTicker(desired_num_ticks=len(palette)+1).
Use the NumeralTickFormatter with the format (0.00 a) to display numbes i the wanted format
See the example below:
from bokeh.palettes import brewer
from bokeh.transform import transform
from bokeh.models import NumeralTickFormatter ,LinearColorMapper, ColorBar, BasicTicker, ColumnDataSource
from bokeh.plotting import figure, show, output_notebook
output_notebook()
source = ColumnDataSource(dict(
x=[1,10,100,200,400,1000,2000,4000],
y=[1,2,3,4,5,6,7,8]
))
palette = brewer['Greys'][8][::-1]
p = figure(width=500, height=300)
color_mapper = LinearColorMapper(palette = palette, low = 0, nan_color = '#d9d9d9')
p.circle('x', 'y', color=transform("x", color_mapper), line_color='black', size=10, source=source)
color_bar = ColorBar(
color_mapper=color_mapper,
label_standoff=8,
width = 425,
height = 20,
location = (0,0),
orientation = 'horizontal',
formatter = NumeralTickFormatter(format='0.00 a'),
ticker=BasicTicker(desired_num_ticks=len(palette)+1),
bar_line_color='black',
major_tick_line_color='black'
)
p.add_layout(color_bar, 'below')
show(p)

Dynamically change the coordinates and the text of annotation with slider in Bokeh plot

I have a Bokeh plot in which I have a slider. I want to change the coordinates of the line drawn with the slider, as shown in the screenshot of the figure. When I change the slider, the line changes its coordinates.
I tried using a slider widget with columndatasource. But, as I am new to Python, I cannot get to move the location and text of the label with the slider. Is there a way to do that?
My code is given below:
import math
import numpy as np
from bokeh.io import output_file
from bokeh.plotting import figure, show
from bokeh.layouts import column, row
from bokeh.models import CustomJS, Slider, Label, LabelSet
from bokeh.plotting import ColumnDataSource, figure, show
from bokeh.models import Arrow, OpenHead, NormalHead, VeeHead
theta = 0 #input the value here
theta = np.radians(-theta)
#Inputs to be made text boxes
sig_x = 10
# line
x=[1,1]
y=[-1,1]
x1=[1,1]
y1=[1,1]
I want to introduce a variable which will change with the slider also, which, for now is 10 here.
sig_1 = 10*sig_x
then i introduced dictionaries, and along with x=x, y=y the x1=x1, y1=y1.
source = ColumnDataSource(data=dict(x=x, y=y))
fig = figure(title = 'Test of Text Rotation',
plot_height = 300, plot_width = 300,
x_range = (-3,3), y_range=(-3,3),
toolbar_location = None)
I could not find a way to add label to the line, so I added layout (from tutorial example). However, unlike fig.line command, the 'x' and 'y' cannot be added as variables (pardon me if i do not use the right jargon).
citation = Label(x=1, y=1, text = str(sig_1))
fig.line('x', 'y',source=source, line_width = 2) # Main Stress block
fig.add_layout(citation)
amp_slider = Slider(start=0, end=360, value=theta, step=1, title="theta")
# Adding callback code,
callback = CustomJS(args=dict(source=source ,val=amp_slider),
code="""
const data = source.data;
var x = data['x'];
var y = data['y'];
var pi = Math.PI;
var theta = -1*(val.value) * (pi/180);
x[0]=(1*Math.cos(theta))-(1*Math.sin(theta)); // addition
x[1]=(1*Math.cos(theta))+(1*Math.sin(theta)); // addition
y[0]=(-1*Math.sin(theta))-(1*Math.cos(theta)); // addition
y[1]=(-1*Math.sin(theta))+(1*Math.cos(theta)); // addition
source.change.emit();
""")
amp_slider.js_on_change('value', callback)
layout = row(fig, column(amp_slider),)
show(layout)
I added the lines of x1[0]=(1*Math.cos(theta))-(1*Math.sin(theta)), x1[1]=(1*Math.cos(theta))+(1*Math.sin(theta));, y[0]=(-1*Math.sin(theta))-(1*Math.cos(theta)); and y[1]=(-1*Math.sin(theta))+(1*Math.cos(theta));
This code, as anticipated does not move the label along with the line. Any explanation of what i am doing wrong, and the possibility of doing it will be very helpful.
You can pass the Lable to the CustomJS-callback as well and modify the values of this model like you do with the ColumnDataSource. Don't forget to call lable.change.emit();.
See the complete example below.
import numpy as np
from bokeh.plotting import figure, show, output_notebook
from bokeh.layouts import row
from bokeh.models import CustomJS, Slider, Label, ColumnDataSource
output_notebook()
theta = 0 #input the value here
theta = np.radians(-theta)
#Inputs to be made text boxes
sig_x = 10
source = ColumnDataSource(data=dict(x=[1,1], y=[-1,1]))
fig = figure(
title = 'Test of Text Rotation',
plot_height = 300,
plot_width = 300,
x_range = (-3,3),
y_range=(-3,3),
toolbar_location = None
)
fig.line('x', 'y',source=source, line_width = 2)
citation = Label(x=1, y=1, text = str(10*sig_x))
fig.add_layout(citation)
amp_slider = Slider(start=0, end=360, value=theta, step=1, title="theta")
# Adding callback code
callback = CustomJS(args=dict(source=source ,val=amp_slider, lable=citation),
code="""
const data = source.data;
var x = data['x'];
var y = data['y'];
var pi = Math.PI;
var theta = -1*(val.value) * (pi/180);
x[0]=(1*Math.cos(theta))-(1*Math.sin(theta));
x[1]=(1*Math.cos(theta))+(1*Math.sin(theta));
y[0]=(-1*Math.sin(theta))-(1*Math.cos(theta));
y[1]=(-1*Math.sin(theta))+(1*Math.cos(theta));
source.change.emit();
lable['x'] = x[1]
lable['y'] = y[1]
lable.change.emit();
"""
)
amp_slider.js_on_change('value', callback)
layout = row(fig, amp_slider)
show(layout)
Result
If you want to modify the text of the lable, you can use a similar approach.

In python bokeh how to modify the field of a fill_color interactively without js?

I am trying to use bokeh to plot the iris data and modify the fill color of the circles interactively but I am running into a problem. I call the plot and the circle with the following:
plot = figure(plot_height=600, plot_width=1000, title="Iris Data",
x_axis_label = 'Sepal length (cm)',
y_axis_label = 'Sepal width (cm)',
tools = "crosshair, pan, reset, save, wheel_zoom")
plot_circle = plot.circle(x='sepal_length', y='sepal_width', source=source,
line_color=None, fill_color={'field':'petal_width','transform':color_mapper},
size='size', fill_alpha = 0.2)
which works but when I try to add the interactivity in the call back it is not clear to me how to modify the 'field' parameter in the fill_color argument to circle. I have tried this:
def update_bubble_color(attrname, old, new):
if new=='petal_width':
color_mapper.low = min(flowers['petal_width'])
color_mapper.high = max(flowers['petal_width'])
fill_color.field='petal_width'
return
if new=='petal_length':
color_mapper.low = min(flowers['petal_length'])
color_mapper.high = max(flowers['petal_length'])
fill_color.field='petal_length'
return
select_bubble_color.on_change('value', update_bubble_color)
the color mapper limits are handled correctly but the colors are not scaled according to the new choice. When I attempt to change it to petal_length with fill_color.field='petal_length' I get an "'name 'fill_color' is not defined" error.
Any help greatly appreciated!
Full code below for reference
import numpy as np
from bokeh.io import curdoc
from bokeh.layouts import row, column
from bokeh.models import ColumnDataSource, LinearColorMapper
from bokeh.models.widgets import Select
from bokeh.plotting import figure
# Load Data
from bokeh.sampledata.iris import flowers
# Global constants (even if python dies not like it)
min_bubble_size = 10
max_bubble_size = 90
def get_scaled_size(vector):
min_vector = min(vector)
max_vector = max(vector)
scaling = (max_bubble_size-min_bubble_size)/(max_vector-min_vector)
scaled_size = [ scaling*(item-min_vector) + min_bubble_size for item in vector]
return scaled_size
# Color Mapper
color_mapper = LinearColorMapper(palette='Inferno256',
low = min(flowers['petal_width']),
high = max(flowers['petal_width']) )
# Define source
flowers['size'] = get_scaled_size(flowers['petal_length'])
source = ColumnDataSource(flowers)
# Set up plot
plot = figure(plot_height=600, plot_width=1000, title="Iris Data",
x_axis_label = 'Sepal length (cm)',
y_axis_label = 'Sepal width (cm)',
tools = "crosshair, pan, reset, save, wheel_zoom")
plot_circle = plot.circle(x='sepal_length', y='sepal_width', source=source,
line_color=None, fill_color={'field':'petal_width','transform':color_mapper},
size='size', fill_alpha = 0.2)
# Set up widgets
select_bubble_size = Select(title ='Bubble size by', value='petal_width',
options = ['petal_width','petal_length'],
width = 200)
select_bubble_color = Select(title ='Bubble color by', value='petal_width',
options = ['petal_width', 'petal_length'],
width = 200)
# Colorbar
from bokeh.models import ColorBar
bar = ColorBar(color_mapper=color_mapper,location=(0,0))
plot.add_layout(bar, 'left')
# Set up callbacks=
# Bubble size call back
def update_bubble_size(attrname, old, new):
if new=='petal_width':
source.data['size'] = get_scaled_size(flowers['petal_width'])
return
if new=='petal_length':
source.data['size'] = get_scaled_size(flowers['petal_length'])
return
select_bubble_size.on_change('value', update_bubble_size)
# bubble color call back
def update_bubble_color(attrname, old, new):
if new=='petal_width':
color_mapper.low = min(flowers['petal_width'])
color_mapper.high = max(flowers['petal_width'])
fill_color.field='petal_width'
return
if new=='petal_length':
color_mapper.low = min(flowers['petal_length'])
color_mapper.high = max(flowers['petal_length'])
fill_color.field='petal_length'
return
select_bubble_color.on_change('value', update_bubble_color)
# Set up layouts and add to document
curdoc().add_root(column(plot, row(select_bubble_size,select_bubble_color), width=800))
curdoc().title = "Iris Data"
fill_color is a property of the glyph, you will need to access it through the glyph:
plot_circle.glyph.fill_color
In your script there is not free variable fill_color anywhere, which is the source of the NameError.

How to add a python callback to pointdrawtool

fairly new to python/bokeh so apologies. I'm trying to use the pointdrawtool to add arrows to my figure on a bokeh server generated plot. I can add it by making it add invisible circles which share the same columndatasource and therefore arrows are drawn but I then want to adjust the arrow start points via a callback so that they are arrows rather than just triangles.
I've tried various things I've seen here and elsewhere but I've so far failed. I don't have a good understanding of what can and cannot produce a callback. If there's a better simpler way of doing it then that would be fine too.
from bokeh.server.server import Server
from bokeh.application import Application
from bokeh.application.handlers.function import FunctionHandler
from bokeh.layouts import layout, row, column
from bokeh.plotting import figure, output_file, show, save, reset_output
from bokeh.models import Label, BoxAnnotation, CustomJS, Slider, Button, ColumnDataSource, BoxEditTool, FreehandDrawTool,PointDrawTool, Arrow, NormalHead
from bokeh.models.widgets import Select
import pandas as pd
import numpy as np
import webbrowser as wb
def make_document(doc):
try:
#set the dimensions for the plot
x_start=1600
x_end=2530
y_start=1800
y_end=5300
#### set up figure
p = figure(plot_width=1000, plot_height=600, x_range=(x_start,x_end),
y_range=(y_end,y_start), tools="pan, wheel_zoom,box_zoom,reset, undo,
redo")
#### set up annotation color and thickness:
thick_ann=10.0
col_ann="yellow"
alpha_ann=0.7
### source dataset and associated code for for any added arrows
#source_ar=ColumnDataSource( {"xs":[0,0],"ys":[0,3],"xe":[1,1], "ye":[1,4]})
source_ar=ColumnDataSource( {"xs":[],"ys":[],"xe":[], "ye":[]})
a1=Arrow(end=NormalHead(size=thick_ann*3, fill_color=col_ann, line_color=col_ann, line_alpha=alpha_ann, fill_alpha=alpha_ann),x_start='xs', y_start='ys', x_end='xs', y_end='ys', source=source_ar, line_color=col_ann, line_width=thick_ann, line_alpha=alpha_ann)
p.add_layout(a1)
### add invisible circle - use this to add and remove arrows
c1=p.circle('xs','ys', size=thick_ann*3,alpha=0.0, source=source_ar)
artool=PointDrawTool(renderers=[c1])
p.add_tools(artool)
#### example callback I think I want to run when adding an arrow via the tool - adjust start
#### values so is actual arrow
def arr_callback(attr, old, new):
source_ar.data["xe"][-1]=source_ar.data["xs"][-1] +5
source_ar.data["ye"][-1]=source_ar.data["ys"][-1] +5
#c1.glyph.data_source.on_change('selected',arr_callback)
doc.add_root(p)
except:
server.stop()
apps = {'/': Application(FunctionHandler(make_document))}
server = Server(apps, port=5003)
server.start()
wb.open('http://localhost:5003', new=2)
Expected result - add a point which adds an invisible circle, an arrow is also drawn and the start point then adjusted so it is an arrow not a triangle.
As far as I know only JS callbacks can be added to the tools in Bokeh in general (CrossHairTool. TapTool, etc...). Unfortunately it is not well documented why some tools doesn't support callbacks at all (like ResetTool or PointDrawTool, etc...). Trying to attach a callback to PointDrawTool gives error.
But if you just want to add a new arrow at each mouse click then another option would be to use e.g. JS callback attached to the plot canvas (see code below for Bokeh v1.0.4). Run the code as python app.py
from tornado.ioloop import IOLoop
from bokeh.server.server import Server
from bokeh.application import Application
from bokeh.application.handlers.function import FunctionHandler
from bokeh.plotting import figure, show
from bokeh.models import CustomJS, ColumnDataSource, Arrow, NormalHead, Segment
def make_document(doc):
p = figure(plot_width = 1000, plot_height = 600, x_range = (0, 10),
y_range = (0, 6), tools = "pan, wheel_zoom,box_zoom,reset,undo,redo")
#### set up annotation color and thickness:
thick_ann = 10.0
col_ann = "red"
alpha_ann = 0.7
#### source dataset and associated code for for any added arrows
source = ColumnDataSource(data = {"xs":[1, 2, 3], "ys":[1, 2, 3], "xe":[4, 5, 6], "ye":[1, 2, 3], 'width': [30] * 3, 'color': [col_ann] * 3 })
a1 = Arrow(end = NormalHead(size = thick_ann * 3, fill_color = col_ann, line_color = col_ann, line_alpha = alpha_ann, fill_alpha = alpha_ann), x_start = 'xs', y_start = 'ys', x_end = 'xe', y_end = 'ye', source = source, line_color = col_ann, line_alpha = alpha_ann)
s1 = p.segment(x0 = 'xs', y0 = 'ys', x1 = 'xe', y1 = 'ye', color = 'color', source = source)
p.add_layout(a1)
code = """ new_x = Number(cb_obj.x);
new_y = Number(cb_obj.y);
data = {xe: [new_x], ys: [new_y], ye: [new_y]};
data['xs'] = [Number(data['xe']) - 3];
data['color'] = ['red'];
data['width'] = [90];
source.stream(data); """
p.js_on_event('tap', CustomJS(args = dict(source = source), code = code))
doc.add_root(p)
io_loop = IOLoop.current()
server = Server(applications = {'/': Application(FunctionHandler(make_document))}, io_loop = io_loop, port = 5001)
server.start()
server.show('/')
io_loop.start()
Result:
I am very new to Bokeh but I wanted to do that this morning and came up with that solution:
from bokeh.plotting import figure, output_notebook, show
from bokeh.models import ColumnDataSource, PointDrawTool
from bokeh.events import Tap
output_notebook()
def bkapp(doc):
p = figure(x_range=(0, 10), y_range=(0, 10), width=400,
height=400, tools=[])
source = ColumnDataSource({
'x': [1, 5, 9], 'y': [1, 5, 9], 'color': ['red', 'green', ' yellow']
})
def callback(event):
source.data["x"].append(event.x)
source.data["y"].append(event.y)
print(source.data)
renderer = p.scatter(x="x", y="y", source=source, color="color",
size=10)
draw_tool = PointDrawTool(renderers=[renderer],
empty_value='black')
p.on_event(Tap, callback)
p.add_tools(draw_tool)
p.toolbar.active_tap = draw_tool
doc.add_root(p)
show(bkapp)
This is using an embedded server in the Jupyter notebook.
I am just drawing points...
Does this help?

Bokeh: vbar not updating but circles do

I'm building a simple plot with bokeh 0.12.13 with a slider to dynamically update. Everything works nicely with circles but not with vbar and I do not understand why.
See code below
def update_title(attrname, old, new):
''' Update figure title when text box changes'''
plot.title.text = text.value
def update_data(attrname, old, new):
''' Update graph using a slider '''
# Get the current slider values
q = quantile.value
# Generate the new curve
X = np.array(range(len(sample_data[q].Index)))+0.35
Y = sample_data[q].Index_reported_Rate
source.data = dict(x=X, y=Y)
text = TextInput(title="title", value='Enter text')
quantile = Slider(start=0, end=5, value=1, step=1, title="quantile")
X = sample_data[0].Index.values
Y = sample_data[quantile.value].Index_reported_Rate.values
source = ColumnDataSource(data=dict(x=X, top=Y))
# Source below works for circles
# source_c = ColumnDataSource(data=dict(x=X, y=Y))
# Definition of the figure
plot = figure(plot_width=500,
plot_height=500,
x_axis_label=selected_factor,
x_range=sample_data[quantile.value].Index.values,
title="Figure"
)
plot.circle(x='x',
y='y',
source=source_c,
size=20
)
### Set up callbacks
text.on_change('value', update_title)
quantile.on_change('value', update_data)
### Set up layouts and add to document
inputs = widgetbox(text, quantile)
curdoc().add_root(row(inputs, plot, width=500))
curdoc().title = "Sliders"
So this code works perfectly fine (except I can't update the axes yet) but if I replace plot.circle with vbar as shown below, then it does not work.
By "not working", I mean that when I run the server, the figure displays the bars but values do not change when I slide the slider....
plot.vbar(x='x',
top='top',
source=source,
width=0.25,
bottom=0,
color='red'
)

Categories