Calling Python function when clicking on a glyph

I know the only way the BokehJS can interact with Python code is though Bokeh server.
I can easily invoke a Python function when clicking e.g. on a Button widget.

Is there any way to call Python code when clicking on a glyph e.g. a circle?

from functools import partial
from bokeh.plotting import curdoc, show, figure
from bokeh.models.widgets import Button, Paragraph
from bokeh.models import CustomJS, ColumnDataSource, TapTool
from bokeh.layouts import widgetbox, column
from bokeh.client import push_session

plot = figure(width = 400, height = 400, tools = “tap”)
circles = plot.circle(x = [2, 5, 8], y = [5, 8, 5], size = 30)

minus = Button(label = ‘-’)
plus = Button(label = ‘+’)
text = Paragraph(text = ‘.’)

def function_plus():
text.text = ‘Python function “function_plus” called’

def function_minus():
text.text = ‘Python function “function_minus” called’

def click_handler(foo = None):
foo()

minus.on_click(partial(click_handler, foo = function_minus))
plus.on_click(partial(click_handler, foo = function_plus))

tool = plot.select_one(TapTool)
tool.renderers = [circles]
tool.callback = CustomJS.from_py_func(click_handler) # how to pass “function_minus” or “function_plus” here?

layout = column(widgetbox(minus, plus, text), plot)

session = push_session(document = curdoc())
curdoc().add_root(layout)
session.show(layout)
session.loop_until_closed()

``

···

On Wednesday, July 26, 2017 at 11:47:46 AM UTC+2, Tony Halik wrote:

I know the only way the BokehJS can interact with Python code is though Bokeh server.
I can easily invoke a Python function when clicking e.g. on a Button widget.

Is there any way to call Python code when clicking on a glyph e.g. a circle?

I am not really familiar with partial but to answer:

“Is there any way to call Python code when clicking on a glyph e.g. a circle?”

from bokeh.plotting import curdoc, show, figure

from bokeh.models.widgets import Button, Paragraph

from bokeh.models import CustomJS, ColumnDataSource, TapTool

from bokeh.layouts import widgetbox, column

from bokeh.client import push_session

from bokeh.events import Tap

from datetime import datetime

plot = figure(width = 400, height = 400, tools = “tap”)

source = ColumnDataSource(data={‘x’:[2, 5, 8],‘y’:[5, 8, 5]})

circles = plot.circle(x =‘x’, y = ‘y’, size = 30, source = source)

minus = Button(label = ‘-’)

plus = Button(label = ‘+’)

text = Paragraph(text = ‘.’,width=500)

def function_plus():

text.text = 'Python function “function_plus” called with button at '+str(datetime.now())

def function_minus():

text.text = 'Python function “function_minus” called with button at '+str(datetime.now())

def function_source(attr,old,new):

if 'function_plus' in text.text:

	function_plus()

else:

	function_minus()

text.text = text.text.replace('button','source')

source.selected.update({"0d":{"glyph":None,"indices":[]},"1d":{"indices":[]},"2d":{"indices":{}}})

source.on_change(‘selected’,function_source)

minus.on_click(function_minus)

plus.on_click(function_plus)

layout = column(widgetbox(minus, plus, text), plot)

session = push_session(document = curdoc())

curdoc().add_root(layout)

session.show(layout)

session.loop_until_closed()

``

Here i reset the selected attribute of the source after a circle is clicked so that you can clikc the same circle several time in a row and still trigger the “selected” event.

I thought it might start an infinite loop but apparently doing source.selected.update doesn’t trigger the source.on_change(‘selected’) so it works.

Cool! This is what I was after.
Thanks a lot!

from bokeh.plotting import curdoc, show, figure

from bokeh.models import ColumnDataSource

from bokeh.client import push_session

source = ColumnDataSource(data = {‘x’:[2, 5, 8], ‘y’:[5, 8, 5]})
plot = figure(tools = “tap”, title = ‘Plot’)

circles = plot.circle(x = ‘x’, y = ‘y’, size = 30, source = source)

def function_source(attr, old, new):

plot.title.text = ‘Circle index: %s’ % source.selected[“1d”][“indices”][0]

source.on_change(‘selected’, function_source)

session = push_session(document = curdoc())

curdoc().add_root(plot)

session.show()

session.loop_until_closed()

``

···

On Wednesday, July 26, 2017 at 4:27:15 PM UTC+2, Sébastien Roche wrote:

I am not really familiar with partial but to answer:

“Is there any way to call Python code when clicking on a glyph e.g. a circle?”

from bokeh.plotting import curdoc, show, figure

from bokeh.models.widgets import Button, Paragraph

from bokeh.models import CustomJS, ColumnDataSource, TapTool

from bokeh.layouts import widgetbox, column

from bokeh.client import push_session

from bokeh.events import Tap

from datetime import datetime

plot = figure(width = 400, height = 400, tools = “tap”)

source = ColumnDataSource(data={‘x’:[2, 5, 8],‘y’:[5, 8, 5]})

circles = plot.circle(x =‘x’, y = ‘y’, size = 30, source = source)

minus = Button(label = ‘-’)

plus = Button(label = ‘+’)

text = Paragraph(text = ‘.’,width=500)

def function_plus():

text.text = 'Python function “function_plus” called with button at '+str(datetime.now())

def function_minus():

text.text = 'Python function “function_minus” called with button at '+str(datetime.now())

def function_source(attr,old,new):

if ‘function_plus’ in text.text:

  function_plus()

else:

  function_minus()

text.text = text.text.replace(‘button’,‘source’)

source.selected.update({“0d”:{“glyph”:None,“indices”:},“1d”:{“indices”:},“2d”:{“indices”:{}}})

source.on_change(‘selected’,function_source)

minus.on_click(function_minus)

plus.on_click(function_plus)

layout = column(widgetbox(minus, plus, text), plot)

session = push_session(document = curdoc())

curdoc().add_root(layout)

session.show(layout)

session.loop_until_closed()

``

Here i reset the selected attribute of the source after a circle is clicked so that you can clikc the same circle several time in a row and still trigger the “selected” event.

I thought it might start an infinite loop but apparently doing source.selected.update doesn’t trigger the source.on_change(‘selected’) so it works.

You can add this to not get an error when you click on something else than a circle

def function_source(attr, old, new):
try:
plot.title.text = ‘Circle index: %s’ % source.selected[“1d”][“indices”][0]
except IndexError:
plot.title.text = ‘Plot’

``

Thank you for your great support!

···

On Wednesday, July 26, 2017 at 7:42:07 PM UTC+2, Sébastien Roche wrote:

You can add this to not get an error when you click on something else than a circle

def function_source(attr, old, new):
try:
plot.title.text = ‘Circle index: %s’ % source.selected[“1d”][“indices”][0]
except IndexError:
plot.title.text = ‘Plot’

``

Hi Sébastien,

I would like to replicate this exact code in Jupyter Notebook. However, when I use Tap or on_click events do no appear to work. Could you please let me know what have I done wrong in the following Jupyter Code snippet?

I’m highly interested in the ability to use a Tap tool in order to tap on a single point and exectue a Python function within a running Jupyter notebook.

from bokeh.plotting import figure, show
from bokeh.io import output_notebook,push_notebook,reset_output

output_notebook()

from bokeh.models.widgets import Button, Paragraph
from bokeh.models import CustomJS, ColumnDataSource, TapTool
from bokeh.layouts import widgetbox, column
#from bokeh.client import push_session
from bokeh.events import Tap
from datetime import datetime

plot = figure(width = 400, height = 400, tools = “tap”)

source = ColumnDataSource(data={‘x’:[2, 5, 8],‘y’:[5, 8, 5]})
circles = plot.circle(x =‘x’, y = ‘y’, size = 30, source = source)

minus = Button(label = ‘-’)
plus = Button(label = ‘+’)
text = Paragraph(text = ‘.’,width=500)

def function_plus():
text.text = 'Python function “function_plus” called with button at '+str(datetime.now())

push_notebook(handle=handler)

def function_minus():
text.text = 'Python function “function_minus” called with button at '+str(datetime.now())

def function_source(attr,old,new):
if ‘function_plus’ in text.text:
function_plus()
else:
function_minus()

#text.text = text.text.replace('button','source')
text.text = text.text.replace('.','source')
source.selected.update({"0d":{"glyph":None,"indices":[]},"1d":{"indices":[]},"2d":{"indices":{}}})

push_notebook(handle=handler)

source.on_change(‘selected’,function_source)

minus.on_click(function_minus)
plus.on_click(function_plus)

layout = column(widgetbox(minus, plus, text), plot)
handler = show(layout,notebook_handle=True)

``

···

On Wednesday, July 26, 2017 at 10:27:15 AM UTC-4, Sébastien Roche wrote:

I am not really familiar with partial but to answer:

“Is there any way to call Python code when clicking on a glyph e.g. a circle?”

from bokeh.plotting import curdoc, show, figure

from bokeh.models.widgets import Button, Paragraph

from bokeh.models import CustomJS, ColumnDataSource, TapTool

from bokeh.layouts import widgetbox, column

from bokeh.client import push_session

from bokeh.events import Tap

from datetime import datetime

plot = figure(width = 400, height = 400, tools = “tap”)

source = ColumnDataSource(data={‘x’:[2, 5, 8],‘y’:[5, 8, 5]})

circles = plot.circle(x =‘x’, y = ‘y’, size = 30, source = source)

minus = Button(label = ‘-’)

plus = Button(label = ‘+’)

text = Paragraph(text = ‘.’,width=500)

def function_plus():

text.text = 'Python function “function_plus” called with button at '+str(datetime.now())

def function_minus():

text.text = 'Python function “function_minus” called with button at '+str(datetime.now())

def function_source(attr,old,new):

if ‘function_plus’ in text.text:

  function_plus()

else:

  function_minus()

text.text = text.text.replace(‘button’,‘source’)

source.selected.update({“0d”:{“glyph”:None,“indices”:},“1d”:{“indices”:},“2d”:{“indices”:{}}})

source.on_change(‘selected’,function_source)

minus.on_click(function_minus)

plus.on_click(function_plus)

layout = column(widgetbox(minus, plus, text), plot)

session = push_session(document = curdoc())

curdoc().add_root(layout)

session.show(layout)

session.loop_until_closed()

``

Here i reset the selected attribute of the source after a circle is clicked so that you can clikc the same circle several time in a row and still trigger the “selected” event.

I thought it might start an infinite loop but apparently doing source.selected.update doesn’t trigger the source.on_change(‘selected’) so it works.

Hi,

on_change, on_event, etc. require using a Bokeh server (the Bokeh server *is the thing* that actually handles getting events to and from JS and python, and calls the python callbacks). You can see an example of embedding a Bokeh server app in a notebook here:

  https://github.com/bokeh/bokeh/blob/master/examples/howto/server_embed/notebook_embed.ipynb

Thanks,

Bryan

···

On Aug 29, 2017, at 09:54, [email protected] wrote:

Hi Sébastien,

I would like to replicate this exact code in Jupyter Notebook. However, when I use Tap or on_click events do no appear to work. Could you please let me know what have I done wrong in the following Jupyter Code snippet?
I'm highly interested in the ability to use a Tap tool in order to tap on a single point and exectue a Python function within a running Jupyter notebook.

from bokeh.plotting import figure, show
from bokeh.io import output_notebook,push_notebook,reset_output

output_notebook()

from bokeh.models.widgets import Button, Paragraph
from bokeh.models import CustomJS, ColumnDataSource, TapTool
from bokeh.layouts import widgetbox, column
#from bokeh.client import push_session
from bokeh.events import Tap
from datetime import datetime

plot = figure(width = 400, height = 400, tools = "tap")

source = ColumnDataSource(data={'x':[2, 5, 8],'y':[5, 8, 5]})
circles = plot.circle(x ='x', y = 'y', size = 30, source = source)

minus = Button(label = '-')
plus = Button(label = '+')
text = Paragraph(text = '.',width=500)

def function_plus():
    text.text = 'Python function "function_plus" called with button at '+str(datetime.now())
    
    push_notebook(handle=handler)
    
def function_minus():
    text.text = 'Python function "function_minus" called with button at '+str(datetime.now())
  
def function_source(attr,old,new):
    if 'function_plus' in text.text:
        function_plus()
    else:
        function_minus()
    
    #text.text = text.text.replace('button','source')
    text.text = text.text.replace('.','source')
    source.selected.update({"0d":{"glyph":None,"indices":},"1d":{"indices":},"2d":{"indices":{}}})
    
    push_notebook(handle=handler)

source.on_change('selected',function_source)

minus.on_click(function_minus)
plus.on_click(function_plus)

layout = column(widgetbox(minus, plus, text), plot)
handler = show(layout,notebook_handle=True)

On Wednesday, July 26, 2017 at 10:27:15 AM UTC-4, Sébastien Roche wrote:
I am not really familiar with partial but to answer:

"Is there any way to call Python code when clicking on a glyph e.g. a circle?"

from bokeh.plotting import curdoc, show, figure
from bokeh.models.widgets import Button, Paragraph
from bokeh.models import CustomJS, ColumnDataSource, TapTool
from bokeh.layouts import widgetbox, column
from bokeh.client import push_session
from bokeh.events import Tap
from datetime import datetime

plot = figure(width = 400, height = 400, tools = "tap")

source = ColumnDataSource(data={'x':[2, 5, 8],'y':[5, 8, 5]})
circles = plot.circle(x ='x', y = 'y', size = 30, source = source)

minus = Button(label = '-')
plus = Button(label = '+')
text = Paragraph(text = '.',width=500)

def function_plus():
    text.text = 'Python function "function_plus" called with button at '+str(datetime.now())
    
def function_minus():
    text.text = 'Python function "function_minus" called with button at '+str(datetime.now())

def function_source(attr,old,new):
  if 'function_plus' in text.text:
    function_plus()
  else:
    function_minus()

  text.text = text.text.replace('button','source')
  source.selected.update({"0d":{"glyph":None,"indices":},"1d":{"indices":},"2d":{"indices":{}}})

source.on_change('selected',function_source)

minus.on_click(function_minus)
plus.on_click(function_plus)

layout = column(widgetbox(minus, plus, text), plot)

session = push_session(document = curdoc())
curdoc().add_root(layout)
session.show(layout)
session.loop_until_closed()

Here i reset the selected attribute of the source after a circle is clicked so that you can clikc the same circle several time in a row and still trigger the "selected" event.
I thought it might start an infinite loop but apparently doing source.selected.update doesn't trigger the source.on_change('selected') so it works.

--
You received this message because you are subscribed to the Google Groups "Bokeh Discussion - Public" group.
To unsubscribe from this group and stop receiving emails from it, send an email to [email protected].
To post to this group, send email to [email protected].
To view this discussion on the web visit https://groups.google.com/a/continuum.io/d/msgid/bokeh/3f218af3-663f-4b56-baf3-ced1c52a586a%40continuum.io\.
For more options, visit https://groups.google.com/a/continuum.io/d/optout\.

FWIW I’ve found it handy to keep a show_app function like this around (code extracted from the example notebook that Bryan linked above).

def show_app(layout):

from bokeh.application import Application

from bokeh.application.handlers import FunctionHandler
from bokeh.io import show

def modify_doc(doc):

doc.add_root(layout)

handler = FunctionHandler(modify_doc)

app = Application(handler)

return show(app)

``

You can basically swap in show_app for the standard bokeh.io.show and then the on_change, on_event, etc. callbacks will work in the notebook. If you show a layout that includes bokeh widgets then the widget callbacks will also work.

Caveats:

  • You have to be careful not to call show_app on the same figure handle more than once because it won’t show up after the first call.
  • Because the app is running in a separate thread than the main notebook kernel you can’t directly update figure properties after calling show_app (Accessing properties works fine though). You have to use the Document.add_next_tick_callback command (see http://bokeh.pydata.org/en/latest/docs/reference/document.html). I’ve wrapped this logic in a helper function called setattr_app:

def setattr_app(model, prop, val):
import functools

doc = model.document

doc.add_next_tick_callback(functools.partial(setattr, model, prop, val))

``

Example usage: setattr_app(my_line_glyph, ‘line_color’, ‘red’)

Once you have these helper functions this is a pretty nice notebook workflow. You get Python callbacks working for figures and widgets and you don’t have to keep track of figure handles or call push_notebook() when updating properties of existing figures.

Side note: Bryan, do you have any concerns with people using this approach and ending up with a few dozen app instances in a single notebook?

-Jon

···

On Thursday, August 31, 2017 at 12:31:20 PM UTC-4, Bryan Van de ven wrote:

Hi,

on_change, on_event, etc. require using a Bokeh server (the Bokeh server is the thing that actually handles getting events to and from JS and python, and calls the python callbacks). You can see an example of embedding a Bokeh server app in a notebook here:

    [https://github.com/bokeh/bokeh/blob/master/examples/howto/server_embed/notebook_embed.ipynb](https://github.com/bokeh/bokeh/blob/master/examples/howto/server_embed/notebook_embed.ipynb)

Thanks,

Bryan

On Aug 29, 2017, at 09:54, [email protected] wrote:

Hi Sébastien,

I would like to replicate this exact code in Jupyter Notebook. However, when I use Tap or on_click events do no appear to work. Could you please let me know what have I done wrong in the following Jupyter Code snippet?

I’m highly interested in the ability to use a Tap tool in order to tap on a single point and exectue a Python function within a running Jupyter notebook.

from bokeh.plotting import figure, show

from bokeh.io import output_notebook,push_notebook,reset_output

output_notebook()

from bokeh.models.widgets import Button, Paragraph
from bokeh.models import CustomJS, ColumnDataSource, TapTool
from bokeh.layouts import widgetbox, column
#from bokeh.client import push_session

from bokeh.events import Tap

from datetime import datetime

plot = figure(width = 400, height = 400, tools = “tap”)

source = ColumnDataSource(data={‘x’:[2, 5, 8],‘y’:[5, 8, 5]})

circles = plot.circle(x =‘x’, y = ‘y’, size = 30, source = source)

minus = Button(label = ‘-’)
plus = Button(label = ‘+’)
text = Paragraph(text = ‘.’,width=500)

def function_plus():
text.text = 'Python function “function_plus” called with button at '+str(datetime.now())

push_notebook(handle=handler)

def function_minus():
text.text = 'Python function “function_minus” called with button at '+str(datetime.now())

def function_source(attr,old,new):

if 'function_plus' in text.text:
    function_plus()
else:
    function_minus()
#text.text = text.text.replace('button','source')
text.text = text.text.replace('.','source')
source.selected.update({"0d":{"glyph":None,"indices":[]},"1d":{"indices":[]},"2d":{"indices":{}}})
push_notebook(handle=handler)

source.on_change(‘selected’,function_source)

minus.on_click(function_minus)

plus.on_click(function_plus)

layout = column(widgetbox(minus, plus, text), plot)
handler = show(layout,notebook_handle=True)

On Wednesday, July 26, 2017 at 10:27:15 AM UTC-4, Sébastien Roche wrote:

I am not really familiar with partial but to answer:

“Is there any way to call Python code when clicking on a glyph e.g. a circle?”

from bokeh.plotting import curdoc, show, figure
from bokeh.models.widgets import Button, Paragraph
from bokeh.models import CustomJS, ColumnDataSource, TapTool
from bokeh.layouts import widgetbox, column
from bokeh.client import push_session

from bokeh.events import Tap

from datetime import datetime

plot = figure(width = 400, height = 400, tools = “tap”)

source = ColumnDataSource(data={‘x’:[2, 5, 8],‘y’:[5, 8, 5]})

circles = plot.circle(x =‘x’, y = ‘y’, size = 30, source = source)

minus = Button(label = ‘-’)
plus = Button(label = ‘+’)
text = Paragraph(text = ‘.’,width=500)

def function_plus():
text.text = 'Python function “function_plus” called with button at '+str(datetime.now())

def function_minus():
text.text = 'Python function “function_minus” called with button at '+str(datetime.now())

def function_source(attr,old,new):

    if 'function_plus' in text.text:
            function_plus()
    else:
            function_minus()
    text.text = text.text.replace('button','source')
    source.selected.update({"0d":{"glyph":None,"indices":[]},"1d":{"indices":[]},"2d":{"indices":{}}})

source.on_change(‘selected’,function_source)

minus.on_click(function_minus)

plus.on_click(function_plus)

layout = column(widgetbox(minus, plus, text), plot)

session = push_session(document = curdoc())
curdoc().add_root(layout)
session.show(layout)
session.loop_until_closed()

Here i reset the selected attribute of the source after a circle is clicked so that you can clikc the same circle several time in a row and still trigger the “selected” event.

I thought it might start an infinite loop but apparently doing source.selected.update doesn’t trigger the source.on_change(‘selected’) so it works.


You received this message because you are subscribed to the Google Groups “Bokeh Discussion - Public” group.

To unsubscribe from this group and stop receiving emails from it, send an email to [email protected].

To post to this group, send email to [email protected].

To view this discussion on the web visit https://groups.google.com/a/continuum.io/d/msgid/bokeh/3f218af3-663f-4b56-baf3-ced1c52a586a%40continuum.io.

For more options, visit https://groups.google.com/a/continuum.io/d/optout.

I have the same problem but with the latest version 1.0.2, anyone knows how to make this in that version?

···

El miércoles, 26 de julio de 2017, 10:20:42 (UTC-5), Tony Halik escribió:

Cool! This is what I was after.
Thanks a lot!

from bokeh.plotting import curdoc, show, figure

from bokeh.models import ColumnDataSource

from bokeh.client import push_session

source = ColumnDataSource(data = {‘x’:[2, 5, 8], ‘y’:[5, 8, 5]})
plot = figure(tools = “tap”, title = ‘Plot’)

circles = plot.circle(x = ‘x’, y = ‘y’, size = 30, source = source)

def function_source(attr, old, new):

plot.title.text = ‘Circle index: %s’ % source.selected[“1d”][“indices”][0]

source.on_change(‘selected’, function_source)

session = push_session(document = curdoc())

curdoc().add_root(plot)

session.show()

session.loop_until_closed()

``

On Wednesday, July 26, 2017 at 4:27:15 PM UTC+2, Sébastien Roche wrote:

I am not really familiar with partial but to answer:

“Is there any way to call Python code when clicking on a glyph e.g. a circle?”

from bokeh.plotting import curdoc, show, figure

from bokeh.models.widgets import Button, Paragraph

from bokeh.models import CustomJS, ColumnDataSource, TapTool

from bokeh.layouts import widgetbox, column

from bokeh.client import push_session

from bokeh.events import Tap

from datetime import datetime

plot = figure(width = 400, height = 400, tools = “tap”)

source = ColumnDataSource(data={‘x’:[2, 5, 8],‘y’:[5, 8, 5]})

circles = plot.circle(x =‘x’, y = ‘y’, size = 30, source = source)

minus = Button(label = ‘-’)

plus = Button(label = ‘+’)

text = Paragraph(text = ‘.’,width=500)

def function_plus():

text.text = 'Python function “function_plus” called with button at '+str(datetime.now())

def function_minus():

text.text = 'Python function “function_minus” called with button at '+str(datetime.now())

def function_source(attr,old,new):

if 'function_plus' in text.text:
  function_plus()
else:
  function_minus()
text.text = text.text.replace('button','source')
source.selected.update({"0d":{"glyph":None,"indices":[]},"1d":{"indices":[]},"2d":{"indices":{}}})

source.on_change(‘selected’,function_source)

minus.on_click(function_minus)

plus.on_click(function_plus)

layout = column(widgetbox(minus, plus, text), plot)

session = push_session(document = curdoc())

curdoc().add_root(layout)

session.show(layout)

session.loop_until_closed()

``

Here i reset the selected attribute of the source after a circle is clicked so that you can clikc the same circle several time in a row and still trigger the “selected” event.

I thought it might start an infinite loop but apparently doing source.selected.update doesn’t trigger the source.on_change(‘selected’) so it works.

There was some confusion and regressions around selections leading up to 1.0. For most use case you would want to use:

  source.selected.on_change('indices', function_source)

This kind of usage is now continuously maintained under integration tests.

On a tangent, please note that using "session.loop_until_closed()" is highly discouraged (I would go so far as to say it is not supported for anything except supporting testing use cases) and has been for quite a long time. It should be printing a giant multi-line warning explaining why when you call it.

Thanks,

Bryan

···

On Dec 11, 2018, at 17:06, [email protected] wrote:

I have the same problem but with the latest version 1.0.2, anyone knows how to make this in that version?

El miércoles, 26 de julio de 2017, 10:20:42 (UTC-5), Tony Halik escribió:
Cool! This is what I was after.
Thanks a lot!

from bokeh.plotting import curdoc, show, figure

from bokeh.models import ColumnDataSource

from bokeh.client import push_session

source = ColumnDataSource(data = {'x':[2, 5, 8], 'y':[5, 8, 5]})
plot = figure(tools = "tap", title = 'Plot')

circles = plot.circle(x = 'x', y = 'y', size = 30, source = source)

def function_source(attr, old, new):

  plot.title.text = 'Circle index: %s' % source.selected["1d"]["indices"][0]

source.on_change('selected', function_source)

session = push_session(document = curdoc())

curdoc().add_root(plot)

session.show()

session.loop_until_closed()

On Wednesday, July 26, 2017 at 4:27:15 PM UTC+2, Sébastien Roche wrote:
I am not really familiar with partial but to answer:

"Is there any way to call Python code when clicking on a glyph e.g. a circle?"

from bokeh.plotting import curdoc, show, figure
from bokeh.models.widgets import Button, Paragraph
from bokeh.models import CustomJS, ColumnDataSource, TapTool
from bokeh.layouts import widgetbox, column
from bokeh.client import push_session
from bokeh.events import Tap
from datetime import datetime

plot = figure(width = 400, height = 400, tools = "tap")

source = ColumnDataSource(data={'x':[2, 5, 8],'y':[5, 8, 5]})
circles = plot.circle(x ='x', y = 'y', size = 30, source = source)

minus = Button(label = '-')
plus = Button(label = '+')
text = Paragraph(text = '.',width=500)

def function_plus():
    text.text = 'Python function "function_plus" called with button at '+str(datetime.now())
    
def function_minus():
    text.text = 'Python function "function_minus" called with button at '+str(datetime.now())

def function_source(attr,old,new):
  if 'function_plus' in text.text:
    function_plus()
  else:
    function_minus()

  text.text = text.text.replace('button','source')
  source.selected.update({"0d":{"glyph":None,"indices":},"1d":{"indices":},"2d":{"indices":{}}})

source.on_change('selected',function_source)

minus.on_click(function_minus)
plus.on_click(function_plus)

layout = column(widgetbox(minus, plus, text), plot)

session = push_session(document = curdoc())
curdoc().add_root(layout)
session.show(layout)
session.loop_until_closed()

Here i reset the selected attribute of the source after a circle is clicked so that you can clikc the same circle several time in a row and still trigger the "selected" event.
I thought it might start an infinite loop but apparently doing source.selected.update doesn't trigger the source.on_change('selected') so it works.

--
You received this message because you are subscribed to the Google Groups "Bokeh Discussion - Public" group.
To unsubscribe from this group and stop receiving emails from it, send an email to [email protected].
To post to this group, send email to [email protected].
To view this discussion on the web visit https://groups.google.com/a/continuum.io/d/msgid/bokeh/dc715efe-b607-4cfe-84a3-186aa8f8b33e%40continuum.io\.
For more options, visit https://groups.google.com/a/continuum.io/d/optout\.

Thanks a lot, it works for me.

···

Jorge Eliecer Valencia Duque