Hi I was able to get my data to stream to a bokeh server but I’m noticing that the xaxis does not dynamically update with it.
Here’s my current test code:
from random import randint
from bokeh.layouts import column
from bokeh.models import Button,ColumnDataSource,FactorRange
from bokeh.plotting import figure, curdoc
# create a plot and style its properties
p = figure(x_range=(0,250), y_range=(9230, 9240), toolbar_location=None)
ds = ColumnDataSource(data=dict(count=[], l4=[]))
# render scatter plot
r = p.circle(x='count', y='l4', source=ds)
i = 0
# create a callback that will get "l4" data for the next count
def button_callback():
curdoc().add_periodic_callback(update, 20)
def update():
global i
new_data = stream_l4_data()
i = i+1
ds.stream(new_data)
def stream_l4_data() -> dict:
global i
return dict(count=[i+1], l4=[randint(9232, 9235)])
# add a button widget and configure with the call back
button = Button(label="Start")
button.on_click(button_callback)
# put the button and plot in a layout and add to the document
curdoc().add_root(column(button, p))
I’m aware I have x_range=(0, 250) and that its limiting my graph, but I haven’t been found anything in the forums or the documentation that allows me to grow the xaxis dynamically as more data is put in. The width will stay the same, so I can possibly shrink the tick mark spacing as time goes on, but the beginning mark of the x axis should be 0 and the last mark should be the final count of how much data has passed though (0 based).
The question is a little confusing. If you want the bounds of the axis to update automatically based on the data, you should omit the x_range=(0, 250) so that the default auto-updating range is used. Is the default auto-updating range doing something you don’t want?
If you mean you want the axis to grow physically, i.e. take up more pixels/space on the screen, there is nothing to make that happen automatically.
It’s not about the callbacks, it’s about the fact that there is no data to start. The DataRange determines the bounds automatically based on the data. If there is no data, then there are no bounds set. If there are no bounds set, the axis can’t render.
You can either:
Make sure at least one data point is there to start, before the callbacks start adding more, or
Add some invisible (i.e. alpha=0) glyph up front, just to give the DataRange something to work with.
Last question, I can open up a new thread if needed but I’m trying to remove the periodic call back. I did some searching and found your post a little while back on removing callbacks from a document.
Here’s my new test code using that source as a baseline to remove a callback as soon as the generator is exhausted:
from random import randint
from collections import Counter
from bokeh.layouts import column
from bokeh.models import Button, ColumnDataSource, FactorRange
from bokeh.driving import count
from bokeh.plotting import figure, curdoc
# create a plot and style its properties
p = figure()
ds = ColumnDataSource(data=dict(count=[], l4=[]))
# add a text renderer to our plot (no data yet)
r = p.circle(x='count', y='l4', source=ds)
i = 0
data_amount = 250
L4_test = (randint(9232, 9235) for _ in range(data_amount))
# create a callback that will get "l4" data for the next count
def button_callback():
curdoc().add_periodic_callback(update, 20)
def update():
global i
global data_amount
if i == data_amount:
curdoc().remove_periodic_callback(update)
return
new_data = stream_l4_data()
ds.stream(new_data)
i = i + 1
def stream_l4_data() -> dict:
global i
return dict(count=[i + 1], l4=[next(L4_test)])
# add a button widget and configure with the call back
button = Button(label="Start")
button.on_click(button_callback)
# put the button and plot in a layout and add to the document
curdoc().add_root(column(button, p))
When I run this snippet using bokeh serve test.py it runs as expected until the generator exhausts. When the generator exhausts the remove_periodic_callback(update) function runs, but now my terminal shows ValueError: callback already ran or was already removed, cannot be removed again. How is the update function still running in the event loop when I specifically turned it off?
How is the update function still running in the event loop when I specifically turned it off ?
Well, this is ultimately a Tornado question, since these adding and removing callbacks are fairly thin wrappers around Tornado APIs. But “remove callback” might technically mean something specific like “don’t schedule any more”, and there may already be a next one scheduled even when the current one starts. Typically remove_periodic_callback is called from “outside” e.g. a button to turn off, not a callback turning itself off, so this is never a worry in that situation. In any case this is evidently just a race condition you will have to handle. Offhand, a try / except that ignores a ValueError is probably the simplest thing to do.