js callback on renderer not working. bug ??? and stability

Hello,

The code below has buttons to add/remove lines (with legend). Tried on 0.12.6dev3 and 0.12.5

Unsure if a bug, but in an attempt to attach js_on_change on renderer visibility with clickable legend,

the callback is called for the first line but not for the next added lines (or very rarely).

also, this is probably a bug, sometimes, after adding a line, we get :

TypeError: undefined is not an object (evaluating ‘M.draw_legend’)

That is a bit troubling. Actually, in a larger framework, this happens a lot and we haven’t found a way to cure it.

Any guidance/workarounds would be helpful,

Thx

import random

from bokeh.io import curdoc

from bokeh.layouts import row,column

from bokeh.plotting import figure

from bokeh.models import Legend,CustomJS

from bokeh.models.widgets import Button

n=30

x = list(range(30))

def v(): return [random.randint(0,100) for r in range(n)]

def get_nglyph(p): return sum([int(r.ref[‘type’]==‘GlyphRenderer’) for r in p.renderers])

ba = Button(label=‘add line’)

bd = Button(label=‘remove line’)

p = figure(plot_width=800, plot_height=250)

r = p.line(x=x,y=v(),legend=‘0’,name=‘0’)

r.js_on_change(‘change:visible’,CustomJS(code=""“console.log(‘change:visible’,cb_obj);”""))

leg = p.legend[0]; leg.click_policy=“hide”

def ba_cb():

r=p.line(x=x,y=v(),legend=str(len(p.legend[0].items)),name=str(len(p.legend[0].items)))

r.js_on_change(‘change:visible’,CustomJS(code=""“console.log(‘change:visible’,cb_obj);”""))

ba.on_click(ba_cb)

def bd_cb():

n_glyph = get_nglyph(p)

if n_glyph==0: return

leg_item = p.legend[0].items.pop()

p.renderers.remove(leg_item.renderers[0])

bd.on_click(bd_cb)

curdoc().add_root(column(row(ba,bd),p))

This is still the case in latest bokeh version 0.12.10/11

Is this a bug or an intended behavior.
I’d be grateful to hear a suggestion about a workaround in latter case ? either I will submit an github issue

thanks

···

On Thursday, May 4, 2017 at 12:11:07 AM UTC+2, chupach wrote:

Hello,

The code below has buttons to add/remove lines (with legend). Tried on 0.12.6dev3 and 0.12.5

Unsure if a bug, but in an attempt to attach js_on_change on renderer visibility with clickable legend,

the callback is called for the first line but not for the next added lines (or very rarely).

also, this is probably a bug, sometimes, after adding a line, we get :

TypeError: undefined is not an object (evaluating ‘M.draw_legend’)

That is a bit troubling. Actually, in a larger framework, this happens a lot and we haven’t found a way to cure it.

Any guidance/workarounds would be helpful,

Thx

import random

from bokeh.io import curdoc

from bokeh.layouts import row,column

from bokeh.plotting import figure

from bokeh.models import Legend,CustomJS

from bokeh.models.widgets import Button

n=30

x = list(range(30))

def v(): return [random.randint(0,100) for r in range(n)]

def get_nglyph(p): return sum([int(r.ref[‘type’]==‘GlyphRenderer’) for r in p.renderers])

ba = Button(label=‘add line’)

bd = Button(label=‘remove line’)

p = figure(plot_width=800, plot_height=250)

r = p.line(x=x,y=v(),legend=‘0’,name=‘0’)

r.js_on_change(‘change:visible’,CustomJS(code=“”“console.log(‘change:visible’,cb_obj);”“”))

leg = p.legend[0]; leg.click_policy=“hide”

def ba_cb():

r=p.line(x=x,y=v(),legend=str(len(p.legend[0].items)),name=str(len(p.legend[0].items)))

r.js_on_change(‘change:visible’,CustomJS(code=“”“console.log(‘change:visible’,cb_obj);”“”))

ba.on_click(ba_cb)

def bd_cb():

n_glyph = get_nglyph(p)

if n_glyph==0: return

leg_item = p.legend[0].items.pop()

p.renderers.remove(leg_item.renderers[0])

bd.on_click(bd_cb)

curdoc().add_root(column(row(ba,bd),p))

I think you can’t really add js callbacks after the page has rendered ?

One way to make it work would be to change a bit the code design and just remake the doc each time you add/remove a line. Doing this also avoids quite a few issues with adding/removing any models in general.

import random

from bokeh.io import curdoc

from bokeh.layouts import row,column

from bokeh.plotting import figure

from bokeh.models import Legend, CustomJS, ColumnDataSource, Button

n=30

x = list(range(30))

def v(): return [random.randint(0,100) for r in range(n)]

data = {0:{‘x’:x,‘y’:v()}}

def add_line():

global data

try:

	last_key = sorted(data.keys())[-1]

except IndexError:

	last_key = -1

data[last_key+1] = {'x':x,'y':v()}

doc_maker()

def remove_line():

global data

try:

	last_key = sorted(data.keys())[-1]

except IndexError:

	print 'No lines to remove'

	return

del data[last_key]

doc_maker()

def doc_maker():

curdoc().clear()

ba = Button(label='add line')

bd = Button(label='remove line')

ba.on_click(add_line)

bd.on_click(remove_line)

p = figure(plot_width=800, plot_height=250)

for key in sorted(data.keys()):

	r = p.line(x='x',y='y',legend=str(key),name=str(key),source=ColumnDataSource(data=data[key]))

	r.js_on_change('change:visible', CustomJS(code="""console.log('change:visible',cb_obj);"""))

if len(data.keys())!=0:

	leg = p.legend[0]; leg.click_policy="hide"

curdoc().add_root(column(row(ba,bd),p))

curdoc().title = ‘title’

doc_maker()

``

Hello Sébastien,

I would be more positive: late bokeh releases enable the inclusion / removal of new models rather well (at least glyphs, figures, buttons, table that i tested so far were success).

The suggestion of declaring everything at start (or after curdoc().clear) does not seem to prevail at present.

It seems that the clear direction in the development of bokeh is to eventually grant these kinds of facilities so that an application can be fully dynamic.

Hopefully, I am not completely wrong on this, waiting for further comments from the developers.

Best,

···

On Friday, November 17, 2017 at 5:26:12 PM UTC+1, Sébastien Roche wrote:

I think you can’t really add js callbacks after the page has rendered ?

One way to make it work would be to change a bit the code design and just remake the doc each time you add/remove a line. Doing this also avoids quite a few issues with adding/removing any models in general.

import random

from bokeh.io import curdoc

from bokeh.layouts import row,column

from bokeh.plotting import figure

from bokeh.models import Legend, CustomJS, ColumnDataSource, Button

n=30

x = list(range(30))

def v(): return [random.randint(0,100) for r in range(n)]

data = {0:{‘x’:x,‘y’:v()}}

def add_line():

global data

try:

  last_key = sorted(data.keys())[-1]

except IndexError:

  last_key = -1

data[last_key+1] = {‘x’:x,‘y’:v()}

doc_maker()

def remove_line():

global data

try:

  last_key = sorted(data.keys())[-1]

except IndexError:

  print 'No lines to remove'
  return

del data[last_key]

doc_maker()

def doc_maker():

curdoc().clear()

ba = Button(label=‘add line’)

bd = Button(label=‘remove line’)

ba.on_click(add_line)

bd.on_click(remove_line)

p = figure(plot_width=800, plot_height=250)

for key in sorted(data.keys()):

  r = p.line(x='x',y='y',legend=str(key),name=str(key),source=ColumnDataSource(data=data[key]))
  r.js_on_change('change:visible', CustomJS(code="""console.log('change:visible',cb_obj);"""))

if len(data.keys())!=0:

  leg = p.legend[0]; leg.click_policy="hide"

curdoc().add_root(column(row(ba,bd),p))

curdoc().title = ‘title’

doc_maker()

``