Timeseries x-axis drifting over time

I have multiple timeseries line plots that I update on an fixed interval (200ms periodic callback, using stream() api).

All figures share the same x-axis:

# create shared x_range and display the past 20 seconds
x_range = DataRange1d()
x_range.follow = "end"
x_range.follow_interval = 1000 * 20 # 20 seconds
x_range.range_padding_units = "percent"
x_range.range_padding = 0.01 # 1% padding
figure1 = figure(width=950, height=600,syncable=False, x_axis_type='datetime', y_axis_location = "right")
if x_range is not None:
    figure1.x_range = x_range

# repeated for each plot

The goal is to display the data of the past 20 seconds. The issue is that the x-axis drifts over time as you can see in the following image:

Streaming looks like this:

data = {"x": [datetime.datetime.now()], "tilt": [alpha]}
source.stream(data, rollover=roll_over_amount)

All plots have different rollover and the frequency of new data varies.

How can I prevent the x-axis drift?

Update: If I set the rollover to 2 then there is no drifting. Rollover in my code varies between 100 and 200 and update frequency is between 1Hz and 10Hz. (-> Frequency is dynamic and not in my control so I cant calculate a rollover window that fits 20 seconds of data for all plots)

I haven’t ever seen this, but also haven’t played with the follow_interval stuff much in years. It’s not really possible to speculate without a complete Minimal Reproducible Example to actually run and investigate.

It seems to have something to do with the different rollover values. Here is a minimal reproducible example:

import datetime
import random
from bokeh.models import ColumnDataSource, DataRange1d, DatetimeTickFormatter
from bokeh.plotting import figure
from bokeh.plotting import curdoc
from bokeh.layouts import gridplot

doc = curdoc()
rnd = random.Random()

# create shared x_range and display the past 10 seconds
x_range = DataRange1d()
x_range.follow = "end"
x_range.follow_interval = 1000 * 10 # 10 seconds
x_range.range_padding_units = "percent"
x_range.range_padding = 0.01 # 1% padding

datetime_tick_formatter = DatetimeTickFormatter(microseconds = "%H:%M:%S", milliseconds = "%H:%M:%S", seconds = "%H:%M:%S", minutes = "%H:%M:%S", hours = "%H:%M:%S", days="%H:%M:%S",months="%H:%M:%S", years="%H:%M:%S",  minsec = "%H:%M:%S" )

figure1 = figure(width=950, height=600, syncable=False, x_axis_type='datetime')
figure2 = figure(width=950, height=600, syncable=False, x_axis_type='datetime')

source1 = ColumnDataSource({"x": [], "value1": []})
source2 = ColumnDataSource({"x": [], "value2": []})

figure1.line("x", "value1", syncable=False, source=source1, legend_label=f"value1")
figure1.scatter("x", "value1", syncable=False, source=source1, legend_label=f"value1")
figure1.xaxis.formatter = datetime_tick_formatter
figure1.x_range = x_range

figure2.line("x", "value2", syncable=False, source=source2, legend_label=f"value2")
figure2.scatter("x", "value2", syncable=False, source=source2, legend_label=f"value2")
figure2.xaxis.formatter = datetime_tick_formatter
figure2.x_range = x_range

def update():
    timestamp = datetime.datetime.now()
    source1.stream({"x": [timestamp], "value1": [1]}, rollover=200)

    # source 2 has a 30% chance to update
    if rnd.random() > 0.7:
        source2.stream({"x": [timestamp], "value2": [1]}, rollover=800)

plots_grid = gridplot(
    [
        [figure1, figure2]
    ],
    toolbar_location="right",
    toolbar_options=dict(),
    sizing_mode="stretch_both",
)

doc.add_root(plots_grid)
doc.add_periodic_callback(update, 50)

I could “fix” this drifting by choosing a rollover value so that for example rollover = 200 / ( 10 / update_rate) meaning that each plot contains data worth of 20 seconds. But like I mentioned I do not have control over the data update frequency. But it would be much better to have a reliable “past XX seconds” x-axis

I’ll have to try out the example directly later, but I should clarify one thing up front. The rollover value has nothing do to with time, it is only a measure of the number of points / “rows” of the CDS to retain after an update. A “past XX seconds” option does not currently exist.

There was, quite some time ago, a brief discussion of adding a callback option to afford more sophisticated rollover computation on the fly:

ColumnDataSource.stream with callable to specify rollover policy · Issue #7024 · bokeh/bokeh · GitHub

But there was never a ton of interest expressed for it, and since then, it has not gotten any attention amidst other priorities.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.