Reputation: 3123
I have a bokeh server app named getcoords.py. I start the server with: bokeh serve getcoords.py
. I have the HoverTool
with a CustomJS
callback function. Besides I have a quad
glyph
with the on_change
configured to trigger a selected
event on the server side. The onTab
function is executed each time I tap on the quad
glyph
. When I tap on the glyph I would like to somehow communicate with the client side and get the pointer coordinates. Here is the code:
import bokeh
import bokeh.plotting
p = bokeh.plotting.figure(plot_height=200,x_range=(0,10),y_range=(0,10))
imquad = p.quad(top=[8], bottom=[2], left=[2], right=[8])
sourceXY = bokeh.models.ColumnDataSource(data=dict(x = [0], y = [0]))
callback_hover = bokeh.models.CustomJS(args=dict(sourceXY=sourceXY), code="""
console.log('Coords:'+sourceXY.data['x'] +','+sourceXY.data['y'])
sourceXY.data['x'] = [cb_data['geometry'].x];
sourceXY.data['y'] = [cb_data['geometry'].y];
sourceXY.trigger('change');
""")
def onHover(attr, old, new):
print "Hover"
counter = 0
def onTab(attr, old, new):
global counter
print "Tap on quad. Coordinates:",sourceXY.data['x'], sourceXY.data['y']
sourceXY.data['x'], sourceXY.data['y'] = [counter],[counter]
counter += 1
sourceXY.trigger('data',None,None)
# unselecting imquad to keep triggering on_change:
new['1d']['indices'] = []
imquad.data_source.on_change('selected',onTab)
hover_tool = bokeh.models.HoverTool(callback=callback_hover)
tap_tool = bokeh.models.TapTool()
p.add_tools(tap_tool)
p.add_tools(hover_tool)
bokeh.io.curdoc().add_root(p)
Here is a screen shoot of the browser showing the JavaScript
console log. The Coords:0,0 1,1 2,2 3,3 4,4 correspond to the moment one click ton the quad glyph and those values are sent from the server to the client browser. The javascript CustomJS
code first shows the value of sourceXY and then replaces it with the x and y data coordinates. As you move the mouse, sourceXY is being updated with those coordinates and as long as you don't tap, those are the once showing in the JS console.
And here is the screen shoot of the console in the server side. Every time the quad glyph is tapped, the onTab(attr, old, new) routine is executed. First it displays the values stored in sourceXY, and then it assigns a global counter value that increases by one every time the routine is executed. Here is were I would like to be able to read the value of sourceXY from the client side, but I haven't been able to do it.
wirelessprv-XX-XXX-XXX-XXX:GetCoords pablo$ bokeh serve getcoords.py
2017-02-25 21:26:00,899 Starting Bokeh server version 0.12.4
2017-02-25 21:26:00,911 Starting Bokeh server on port 5006 with applications at paths ['/getcoords']
2017-02-25 21:26:00,912 Starting Bokeh server with process id: 36965
2017-02-25 21:26:01,267 200 GET /getcoords (::1) 85.38ms
2017-02-25 21:26:01,785 WebSocket connection opened
2017-02-25 21:26:01,788 ServerConnection created
Tap on quad. Coordinates: [0] [0]
Tap on quad. Coordinates: [0] [0]
Tap on quad. Coordinates: [1] [1]
Tap on quad. Coordinates: [2] [2]
Tap on quad. Coordinates: [3] [3]
What I have attempted is create a ColumnDataSource
named sourceXY that is updated in the CustomJS
client side. Then when I tap the glyph, the python code on the server side reads the value of the server side ColumnDataSource
, that has not been updated, and then modifies it to test the server-to-client communication. That part works nice, because the client is able to read the x and y sent from the server.
I would like to know if there is a way to get the coordinates saved in the ColumnDataSource
(or the coordinates itself when the tap happens) from the client to the server side.
Any suggestions, comments are welcome. Thanks.
Upvotes: 3
Views: 1173
Reputation: 3123
I found a way to update the coordinate values from the server to the client. I found the solution updating a TextInput
model from the CustomJS
javascript callback from HoverTool
. I'll put my solution here in case someone can benefit from it.
import bokeh
import bokeh.plotting
p = bokeh.plotting.figure(plot_height=200,x_range=(0,10),y_range=(0,10))
imquad = p.quad(top=[8], bottom=[2], left=[2], right=[8])
textxy = bokeh.models.TextInput(title="xy val", value='')
callback_hover = bokeh.models.CustomJS(args=dict(textxy=textxy), code="""
textxy.value = cb_data['geometry'].x + ',' + cb_data['geometry'].y;
console.log(textxy.value);
""")
def onTab(attr, old, new):
print "tap:",textxy.value
# unselecting imquad to keep triggering on_change:
new['1d']['indices'] = []
imquad.data_source.on_change('selected',onTab)
hover_tool = bokeh.models.HoverTool(callback=callback_hover)
tap_tool = bokeh.models.TapTool()
p.add_tools(tap_tool)
p.add_tools(hover_tool)
bokeh.io.curdoc().add_root(p)
The output of the console shows the right coordinates every time I tap on the quad
glyph
:
wirelessprv-XXX-XXX-XXX-XXX:GetCoords pablo$ bokeh serve getcoords.py
2017-02-26 18:09:44,189 Starting Bokeh server version 0.12.4
2017-02-26 18:09:44,199 Starting Bokeh server on port 5006 with applications at paths ['/getcoords']
2017-02-26 18:09:44,199 Starting Bokeh server with process id: 42626
2017-02-26 18:09:46,841 200 GET /getcoords (::1) 68.90ms
2017-02-26 18:09:47,282 WebSocket connection opened
2017-02-26 18:09:47,283 ServerConnection created
tap: 3.3528435714104643,3.925695345068399
tap: 5.715666419702689,6.670813794893257
tap: 6.649805685306592,3.341627589786514
tap: 7.913641162300107,2.407119181335499
tap: 7.913641162300107,7.66372897887246
Update for new Bokeh versions. Tested on 0.12.16
For this approach to work the TextInput model needs to be added to the client layout. Here it is added as part of a row and its disabled:
import bokeh
import bokeh.plotting
p = bokeh.plotting.figure(plot_height=200,x_range=(0,10),y_range=(0,10))
imquad = p.quad(top=[8], bottom=[2], left=[2], right=[8])
textxy = bokeh.models.TextInput(title="xy val", value='',disabled=True)
callback_hover = bokeh.models.CustomJS(args=dict(textxy=textxy), code="""
textxy.value = cb_data['geometry'].x + ',' + cb_data['geometry'].y;
console.log(textxy.value);
""")
def onTab(attr, old, new):
print "tap:",textxy.value
imquad.data_source.on_change('selected',onTab)
hover_tool = bokeh.models.HoverTool(callback=callback_hover)
tap_tool = bokeh.models.TapTool()
p.add_tools(tap_tool)
p.add_tools(hover_tool)
layout = bokeh.layouts.row(p,textxy)
bokeh.io.curdoc().add_root(layout)
It should be mentioned that a bokeh.models.Div
model could serve in the same way to send the x,y coordinates updated in a CustomJS
callback funtion.
Upvotes: 3