bokeh / bokeh

Interactive Data Visualization in the browser, from Python
https://bokeh.org
BSD 3-Clause "New" or "Revised" License
19.23k stars 4.18k forks source link

Error message on the browser console when using CustomJS with args in serve mode #7970

Open thomasetter opened 6 years ago

thomasetter commented 6 years ago

Bokeh 0.12.16, tornado 5.0.2, Python 3.6.5 on Linux, tried both Firefox 60 and Chromium 67

If run with bokeh html --show example.py everything runs as expected (on the browser console we can see no errors).

If run with bokeh serve --show example.py we get an error message on the browser console, e.g. Error: reference {"id":"2e235317-bf21-4a8c-9057-6d1801ded9b8","type":"DataRange1d"} isn't known (not in Document?)

Both correctly install the CustomJS handler and show updates to the x_range in the browser console.

from bokeh.plotting import figure
from bokeh.layouts import layout
from bokeh.models import Div, CustomJS
from bokeh.io import curdoc

# prepare some data
x = [1, 2, 3, 4, 5]
y = [6, 7, 2, 4, 5]

# create a new plot with a title and axis labels
p = figure(title="simple line example", x_axis_label='x', y_axis_label='y')

# add a line renderer with legend and line thickness
p.line(x, y, legend="Temp.", line_width=2)

div = Div()
code = """
        console.log('' + range.start + ' to ' + range.end);
        """

p.x_range.callback = CustomJS(args=dict(range=p.x_range), code=code)

l = layout([[p]])

curdoc().add_root(l)
mattpap commented 6 years ago

For now you can replace range with cb_obj and remove args=... from CustomJS, to avoid this issue.

afausti commented 6 years ago

See also https://groups.google.com/a/continuum.io/forum/#!msg/bokeh/_3QRnNbLM04/1T5ryP1vCgAJ

omearac commented 6 years ago

I'm also seeing this on 0.13 except for me, the callbacks do not work anymore.

Also, @mattpap , by removing the args=... it's impossible to have the callback edit any other bokeh objects which wernt the cb_obj...

Edit: I consider this a breaking change and massive regression since now no user interaction works with 0.13.

bryevdv commented 6 years ago

@omearac As far as I understand it, the error message is merely a nuisance, but does not actually prevent any callbacks from functioning. In particular, the code at the top of this issue prints a console error, but otherwise function. Do you have a counter-example to this that you can provide? Are you sure that your problem is not actually different one and that you aren't mis-ascribing the root cause? As always, real, actual, complete code examples are the best preventative to misunderstanding and miscommunication

since now no user interaction works with 0.13.

Even if there is an example, this is an overstatement. No python callbacks are affected at all by this issue, and arguably most bokeh apps have only python callbacks. And no CustomJS in standalone documents are not affected either, and arguably the vast majority of bokeh usage is standalone documents.

bryevdv commented 6 years ago

@mattpap do you have already have any thought on how to resolve this? I am working on making bokeh server apps end-to-end testable in selenium tests and am running up against this since I am using custom actions to inspect e.g. round-trip state from a widget event

Edit: to be clear "things work" but I have to disable the check for no console errors, since there are spurious errors in the console

bryevdv commented 6 years ago

@omearac are you able to provide an example where callbacks do not work anymore? I suspect, you are running into some other issue, and would like to clarify the situation, but it is impossible to say without code.

omearac commented 6 years ago

@bryevdv yea sorry, I didnt mean to say 'no user interaction works', its just that my webapp makes heavy heavy use of customjs callbacks where I always pass other bokeh models into them via a dict.

This is my custom code which calls another js file loaded to the webpage (bokeh div is embedded in django app). You can see how I often attach callbacks:

hp_callback = CustomJS(args=dict(source=hp_table_source), code="""
        var data = source.data;
        console.log('Selected rows:');
        var indices = source.selected["1d"].indices;

        for (var i = 0; i<indices.length; i++)
        {
           var selectedDate = data['date'][indices[i]];
           var paramName = data['parametername'][indices[i]];
           Athmos.tableRedirect(selectedDate, paramName);
        }
    """)

hp_table_source.callback = hp_callback

In 0.13 these sorts of callback-addings don't work for me. Anyways I reverted back to use 0.12.15 for now, like you said maybe I just have to refactor everything to add callbacks differently but for now that will have to wait. thanks for the help!

bryevdv commented 6 years ago

@omearac what does "doesn't work" mean? The callbacks don't execute at all? They execute with an error? (what is it?) They execute and silently fail? (What state shows up wrong in console logs?) Can you provide a complete example for us to investigate? Offhand I am still having trouble seeing that whatever you are seeing is related to this specific issue, and and uncertainty makes the tracker harder to deal with, but it's not possible to say anything for sure until we can run actual code.

As an aside, the better way to do things in 0.13.0 and newer, as described in the migration notes, is to refer to source.selected.indices, i.e. no more dict with "1d" etc.

dwangus commented 5 years ago

Hi, not sure if I can be of any help or if this issue has been largely resolved so far, but I ran into a similar issue but seemed to find what was making it fail? Here are the examples:

from functools import partial
from functools import thread

from functools import partial
from threading import Thread
import time

from bokeh.layouts import column
from bokeh.models import CustomJS, ColumnDataSource, Slider
from bokeh.plotting import curdoc, figure

from tornado import gen

# this must only be modified from a Bokeh session callback
source = ColumnDataSource(data=dict(x=[0], y=[0]))

# This is important! Save curdoc() to make sure all threads
# see the same document.
doc = curdoc()

@gen.coroutine
def update(x, y):
    source.stream(dict(x=[x], y=[y]))

i = 1
def blocking_task():
    global i
    while True:
        # do some blocking computation
        time.sleep(1)
        x, y = i, i
        i += 1

        # but update the document from callback
        doc.add_next_tick_callback(partial(update, x=x, y=y))

p = figure(plot_width=400, plot_height=400)
p.line(x='x', y='y', source=source)

'''
#NO ERROR IN CONSOLE LOGGED
callback = CustomJS(args=dict(source=source), code="""
        var data = source.data;
        var f = cb_obj.value
        var x = data['x']
        var y = data['y']
        for (var i = 0; i < x.length; i++) {
            y[i] = Math.pow(x[i], f)
        }
        source.change.emit();
    """)

slider = Slider(start=0.1, end=4, value=1, step=.1, title="power", callback=callback)
doc.add_root(column(slider, p))
#'''

'''
#ERROR IN CONSOLE LOGGED BUT CALLBACK EXECUTES JUST FINE
callback = CustomJS(args=dict(source=source), code="""
        var data = source.data;
        var f = 2;
        var x = data['x'];
        var y = data['y'];
        for (var i = 0; i < x.length; i++) {
            y[i] = Math.pow(x[i], f);
        }
        source.change.emit();
    """)

source.js_on_change('streaming', callback)#taken directly from the example at: https://bokeh.pydata.org/en/latest/docs/reference/model.html#bokeh.model.Model.js_on_change
doc.add_root(p)
#'''

#'''
#THIS PRINTS OUT ERROR AND FAILS COMPLETELY
callback = CustomJS(args=dict(source=source), code="""
        var data = source.data;
        var f = 2;
        var x = data['x'];
        var y = data['y'];
        for (var i = 0; i < x.length; i++) {
            y[i] = Math.pow(x[i], f);
        }
        source.change.emit();
    """)

doc.add_root(p)
source.js_on_change('streaming', callback)#taken directly from the example at: https://bokeh.pydata.org/en/latest/docs/reference/model.html#bokeh.model.Model.js_on_change
#'''

thread = Thread(target=blocking_task)
thread.start()

For reference, this was the error I was getting:

[bokeh] Failed to repull session Error: reference {"id":"cf0748ad-bed5-4f2c-88fa-31d03060fb6e","type":"CustomJS"} isn't known (not in Document?)

While I now know to avoid this, can someone explain to me why exactly this fails in this order? I just started using Bokeh a week ago.

bryevdv commented 5 years ago

doc.add_root can only collects things it can reach at the time it is called. By calling js_on_change after doc.add_root the CustomJS object is never actually collected into the document, as the error states.

bkoseoglu commented 5 years ago

@mattpap @thomasetter @bryevdv I was able to run the first code posted by @thomasetter without any error. I just replaced title and toolbar_location keyword arguments with None respectively and code worked as expected. There may be some issue with inheritance between bokeh models or simply there may be some issue with respect to respective libraries.

Upon looking at the source code and master branch. I didn't come across the classes imported by figure, such as Title. It may be related with that. If it is could you please tell me how I can fix it maybe? So that I can contribute to Bokeh environment with which I developed tools and appreciated so much since beginning.

bryevdv commented 5 years ago

@bkoseoglu I don't actually think there is anything at all on the python side (e.g. class inheritance) that is the issue. The problem here is to do with the wire protocol and serialization, on the JavaScript side.

sohailsomani commented 5 years ago

How would one go about fixing this on the JavaScript side? This is causing problems for me.

mattpap commented 1 year ago

Seems to be all fixed now. Tested with:

from bokeh.plotting import figure
from bokeh.models import CustomJS
from bokeh.io import curdoc

x = [1, 2, 3, 4, 5]
y = [6, 7, 2, 4, 5]

p = figure(title="simple line example", x_axis_label='x', y_axis_label='y')
p.line(x, y, legend_label="Temp.", line_width=2)

code = """
console.log(`${range.start} to ${range.end} (added ${when} add_root())`)
"""

p.x_range.js_on_change("change", CustomJS(args=dict(range=p.x_range, when="before"), code=code))
curdoc().add_root(p)
p.x_range.js_on_change("change", CustomJS(args=dict(range=p.x_range, when="after"), code=code))

As always, needs a regression test to close.