☰Read-only archive⌕
Looking for help with Bokeh?

This is a read-only archive. Ask questions and join the conversation on GitHub.

Visit Community Support on GitHub →
Archived discussions · Captured October 28, 2026

Updating y_range with CustomJS callback

Community Support
woody
woody

Hi,
I plot a time serie, along with a WheelZoomTool to zoom in & out (on the x_axis).
I’m trying to auto scale the y_range based on the line plot displayed.

import bokeh
import numpy as np
import pandas as pd

import bokeh.plotting

time_series = pd.Series(np.random.random(1000) - .49).cumsum().to_frame('data').rename_axis('ts').reset_index()
source_ref = bokeh.plotting.ColumnDataSource(data=time_series)
source_disp = bokeh.plotting.ColumnDataSource(data=time_series)

""" Define tools """
pan = bokeh.models.PanTool(dimensions='width')
wheel = bokeh.models.WheelZoomTool(dimensions='width', maintain_focus=True)

figure = bokeh.plotting.figure(title='Performance Report',
                               x_range=bokeh.models.Range1d(time_series.index[0], time_series.index[-1],
                                                            min_interval=15,
                                                            bounds=(time_series.index[0],  # set limit to range
                                                                    time_series.index[-1])),
                               tools=[pan, wheel],
                               active_drag=pan,
                               active_scroll=wheel,
                               y_axis_label='Plot',
                               x_axis_label='Time')

figure.line(x='index', y='data', source=source_disp)

figure.x_range.js_on_change('end',
                            bokeh.models.CustomJS(args={
                                # 'figure': figure,
                                'y_range': figure.y_range,
                                'source': source_ref,
                                'source_disp': source_disp},
                                code=
                                """
                                let i = Math.max(Math.floor(cb_obj.start), 0);
                                let j = Math.min(Math.ceil(cb_obj.end), source.data['data'].length);
                                let max = Math.max.apply(null, source.data['data'].slice(i, j));
                                
                                const ref = source.data;
                                const data = source_disp.data;

                                data["index"] = ref["index"].slice(i, j);
                                data["ts"] = ref["ts"].slice(i, j);
                                data["data"] = ref["data"].slice(i, j);
                                source_disp.change.emit();

                                console.log("y_range end" + y_range.end);
                                y_range.end = max;
                                y_range.change.emit();
                                console.log("y_range end" + y_range.end);
                                console.log("end callback");
                                """
                            ))
bokeh.plotting.show(figure,
                    filename=filepath)

Here we can see that when I zoom in, the y_range scale does not adjust accordingly

⌁   Link to post
woody
woody

I console.log the value of y_range.end before and after the emit.
It seems that in the JS code, the y_range.end is updated.
However, when I continue scrolling in/out, the y_range.end value keep the same, which means that the emit function did not propagated the update to the actual figure.y_range.
That is indeed what we observe, since the chart does not scale when zooming in/out.

⌁   Link to post
Bryan
Bryan

Well, actually you need to describe the interaction you are looking for in detail. Do you mean scrolling the scroll zoom tool? Because by default that updates both ranges, so it would be competing / conflicting with anything in the JS callback. You would presumably want to restrict the scroll zoom to update only the x-axis and not touch the y-axis.

⌁   Link to post
Bryan
Bryan

All that said, I am still a little confused by the behaviour and I am afraid I don’t have any immediate solutions for you. I will need to pare down the example more to investigate. Why are you slicing the CDS columns, for instance? What’s wrong with the full data being in the CDS, just part of it off-screen?

⌁   Link to post
woody
woody

Hello Bryan,
When scrolling with the scroll zoom tool, by default, it zooms proportionately on both x & y axis.
What I’m trying to achieve, is to scroll on x_axis, and have y that auto scale in order to have a neat and accurate view of the data. So I set the scroll only on the x_axis (dimensions=‘width’), and I’m trying to make the change on the y_axis accordingly.

Here are couple of ideas that I implemented, but none was working for me:

  • I originally tried to slice the data, assuming bokeh would fit the y_axis based on the data.
    with only a subset of the data in source, I thought it would focus on only those data.
  • I tried to work with the “only_visible=True” parameters of the axis
  • I tried to set start & end to None, to force an autofit
  • I tried to pass the whole figure as arg to CustomJS, and update figure.y_range.start / end there

From what I can see, the only thing that i’m not able to do, is to have the “y_range.change.emit();” to actually propagatee the change to the plot. It just seems like y_range is set at the beginning when creating the figure, and immutable from then.

If you have any idea, that I could investigate, please let me know.

⌁   Link to post
Bryan
Bryan

@woody I think your computations are off. I don’t have any more time to look at this just now, but here is a simplified (working) version that updates the y-range to the min/max of the viewport data on a button push. I think you can use it as a reference:

import numpy as np
from bokeh.models import Button, ColumnDataSource, CustomJS
from bokeh.plotting import column, figure, show

x = np.linspace(0, 20, 200)
y = x**2

source = ColumnDataSource(data=dict(x=x, y=y))

p = figure(tools="xpan")
p.line("x", "y", source=source)

button = Button()
button.js_on_click(
    CustomJS(args={"xr": p.x_range, "yr": p.y_range, "source": source}, code="""
        const i = Math.max(source.data.x.findIndex((x) => x > xr.start), 0)

        const j0 = source.data.x.findIndex((x) => x > xr.end)
        const j = j0 >= 0 ? j0 : source.data.x.length - 1

        const ysub = source.data.y.slice(i, j)

        yr.start = Math.min.apply(null, ysub);
        yr.end = Math.max.apply(null, ysub);
    """)
)

show(column(p, button))
⌁   Link to post
woody
woody

Bryan,
Thank you very much for your help. From your example, I was able to solve my problem.
It seems like triggering events from a range object (figure.x_range in my case) prevented to change the range value (y_range here).

I replaced it with

figure.js_on_event(bokeh.events.RangesUpdate, jscode)

and now it works great.
Thank you

⌁   Link to post

End of archived discussion · Replies are disabled