简体   繁体   中英

Bokeh plot using a function of values in ColumnDataSource

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.

Minimal Example

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)

Result

通过其他更改的渲染器更新渲染器

Comment

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM