I'd like to plot a glyph using a function of the values in a ColumnDataSource instead of the raw values.
As a minimum example, suppose I want to plot a point that the user can drag, which moves another point around. The position of the second point is some arbitrary function of the position of the first point.
I can draw the second point right on top of the first point like so:
from bokeh.plotting import figure, show
from bokeh.models import PointDrawTool, ColumnDataSource, Circle
p = figure(x_range=(0, 10), y_range=(0, 10), tools=[],
title='Point Draw Tool')
source = ColumnDataSource({
'x': [1], 'y': [1], 'color': ['red']
})
# plot a point at x,y
renderer = p.scatter(x='x', y='y', source=source, color='color', size=10)
# create a circle at the same position as the point
glyph = Circle(x='x',y='y', size=30)
p.add_glyph(source, glyph)
# allow the user to move the point at x,y
draw_tool = PointDrawTool(renderers=[renderer], empty_value='black')
p.add_tools(draw_tool)
p.toolbar.active_tap = draw_tool
show(p)
This plot draws the second point with exactly the same (x,y)
coordinates as the first point.
Now, suppose I'd like to draw the second point at some arbitrary function of the first point (f(x,y), g(x,y))
for a pair of arbitrary functions f()
and g()
. To make it simple, let's say the coordinates of the new point are (2x, 2y)
.
Can someone tell me how to do this?
I'd like to do something like
glyph = Circle(x=2*'x',y=2*'y', size=30)
p.add_glyph(source, glyph)
Clearly this is not how to do it, but hopefully this makes it clear what I'm trying to do. In general, I'd like to do something like:
glyph = Circle(x=f('x','y'), y=g('x','y'), size=30)
p.add_glyph(source, glyph)
I have tried something silly like the following:
def get_circle(x,y):
new_x = 2*x
new_y = 2*y
return Circle(x=new_x, y=new_y, size=30)
You can create a ColumnDataSource
and use a CustomJS
to apply changes to your data.
In the example below I create a default source
, add two renderers to a figure, define a JavaScript callback and excecute the callback every time the source
changes.
Because I link the only one renderer to the PointDrawTool
, the second renderer is updated, after I moved the frist renderer.
from bokeh.plotting import figure, show, output_notebook
from bokeh.models import CustomJS, ColumnDataSource, PointDrawTool
output_notebook()
source = ColumnDataSource(dict(
x1=[1,3],
y1=[2,3],
x2=[2,6],
y2=[6,9]
))
p = figure(width=300, height=300)
r1 = p.circle(x='x1', y='y1', color='blue', source=source, size=5)
r2 = p.triangle(x='x2', y='y2', color='green', source=source, size=5)
draw_tool = PointDrawTool(renderers=[r1], empty_value='black')
p.add_tools(draw_tool)
callback = CustomJS(args=dict(source=source),
code="""
function f(x) {
return x*2;
};
function g(y) {
return y*3;
};
let data = source.data
data['x2'] = data['x1'].map(f)
data['y2'] = data['y1'].map(g)
source.change.emit()
"""
)
source.js_on_change('data', callback)
show(p)
The example can have even more cirlce
s and triangle
s but at the moment only one can be moved and updated at a time and the order matters.
Really hope this helps you.