Axis no longer auto-scales after switching documents

Hi,

I have a little Bokeh server application where I switch between different plots by changing the root of curdoc(). While that works as intended, there is one persistent problem I could not solve: After removing a model containing an auto-scaling axis from the current document and re-adding it again, the axis no longer scales automatically. I have a minimal example:

import math

import bokeh.io
import bokeh.layouts
import bokeh.models
import bokeh.plotting

def gen_data(start, stop):
“”"
Just some example data
“”"
step = 0.25
steps = [start + i * step for i in range(int((stop - start) / step))]
values = [math.cos(x) for x in steps]
return dict(x=steps, y=values)

def update_source(attr, old, new):
“”"
Callback for Slider: Change the data in the plot
“”"
global source
source.data = gen_data(new[0], new[1])

def go_to_model2():
“”"
Callback for ‘next’-button: Change the current document
“”"
global model2
bokeh.io.curdoc().clear()
bokeh.io.curdoc().add_root(model2)

def restore_model1():
“”"
Callback for ‘previous’-button: Restore the original plot
“”"
global model1
bokeh.io.curdoc().clear()
bokeh.io.curdoc().add_root(model1)

Plot some data

source = bokeh.models.ColumnDataSource(data=gen_data(0, 3))
figure = bokeh.plotting.figure()
renderer = figure.line(source=source, x=‘x’, y=‘y’)

Add a slider to control what data is plotted

slider = bokeh.models.RangeSlider(start=0, end=10, value=(0, 3))
slider.on_change(‘value’, update_source)

A button to switch to the next page/model

next_button = bokeh.models.Button(label=‘Next’)
next_button.on_click(go_to_model2)

``# A button to switch to the previous page/model
`back_button = bokeh.models.Button(label=‘Previous’)
back_button.on_click(restore_model1)

`
model1 = bokeh.layouts.column(next_button, slider, figure)
model2 = bokeh.layouts.column(back_button)

bokeh.io.curdoc().add_root(model1)

``

As you can see, the x-axis of the plot updates automatically if you use the slider. But after using the next and previous buttons that is no longer the case. The data gets updated just fine, as you can see by zooming out, but the axis does not change.

How can I solve this issue? I find the auto-scaling quite important for the usability of the document.

I am also not sure if I did the switching between models correctly. I did not find any examples on this topic, thus I made up my own method. I am open to suggestions how to do this better, as I have the suspicion that my current method is the root of my problem.

On Bokeh 1.0.4 adding the following to the callback will do the job:

global figure
figure.x_range = bokeh.models.DataRange1d()

``

···

On Monday, March 18, 2019 at 5:26:38 PM UTC+1, Sebastian Meinhardt wrote:

Hi,

I have a little Bokeh server application where I switch between different plots by changing the root of curdoc(). While that works as intended, there is one persistent problem I could not solve: After removing a model containing an auto-scaling axis from the current document and re-adding it again, the axis no longer scales automatically. I have a minimal example:

import math

import bokeh.io
import bokeh.layouts
import bokeh.models
import bokeh.plotting

def gen_data(start, stop):
“”"
Just some example data
“”"
step = 0.25
steps = [start + i * step for i in range(int((stop - start) / step))]
values = [math.cos(x) for x in steps]
return dict(x=steps, y=values)

def update_source(attr, old, new):
“”"
Callback for Slider: Change the data in the plot
“”"
global source
source.data = gen_data(new[0], new[1])

def go_to_model2():
“”"
Callback for ‘next’-button: Change the current document
“”"
global model2
bokeh.io.curdoc().clear()
bokeh.io.curdoc().add_root(model2)

def restore_model1():
“”"
Callback for ‘previous’-button: Restore the original plot
“”"
global model1
bokeh.io.curdoc().clear()
bokeh.io.curdoc().add_root(model1)

Plot some data

source = bokeh.models.ColumnDataSource(data=gen_data(0, 3))
figure = bokeh.plotting.figure()
renderer = figure.line(source=source, x=‘x’, y=‘y’)

Add a slider to control what data is plotted

slider = bokeh.models.RangeSlider(start=0, end=10, value=(0, 3))
slider.on_change(‘value’, update_source)

A button to switch to the next page/model

next_button = bokeh.models.Button(label=‘Next’)
next_button.on_click(go_to_model2)

``# A button to switch to the previous page/model
`back_button = bokeh.models.Button(label=‘Previous’)
back_button.on_click(restore_model1)

`
model1 = bokeh.layouts.column(next_button, slider, figure)
model2 = bokeh.layouts.column(back_button)

bokeh.io.curdoc().add_root(model1)

``

As you can see, the x-axis of the plot updates automatically if you use the slider. But after using the next and previous buttons that is no longer the case. The data gets updated just fine, as you can see by zooming out, but the axis does not change.

How can I solve this issue? I find the auto-scaling quite important for the usability of the document.

I am also not sure if I did the switching between models correctly. I did not find any examples on this topic, thus I made up my own method. I am open to suggestions how to do this better, as I have the suspicion that my current method is the root of my problem.

Interesting. That certainly solved my problem, thanks. It also gave me an idea how to dig a bit deeper: I found that resetting the start and end properties of the x_range to None also does the trick. Replacing the range with a new object is not required. That is neat, if you have more complex requirements for your axes that you do not want to save and restore all the time (as I do in my original application).

I also noticed, that the y_axis in my example suffers the same problem with the same solution. That was to be expected.

That leaves me with the problem that my original model is of course a little more complicated than this little example I gave here. Keeping track of all axes of all plots and whether they are visible or not does not sound like a good solution to me. So, what is a good solution here? I can recurse through the model that I switch to, searching for all figure models, resetting their respective ranges (and not forgetting about extra_x_ranges and extra_y_ranges!):

def reset_ranges(model):
def reset(range):
range.start = None
range.end = None

if hasattr(model, 'children'):
    for child in model.children:
        reset_ranges(child)
if isinstance(model, bokeh.plotting.Figure):
    reset(model.x_range)
    reset(model.y_range)
    for _, item in model.extra_x_ranges.items():
        reset(item)
    for _, item in model.extra_y_ranges.items():
        reset(item)

``

I used this code on my original app and it worked. But it strikes me as a convoluted solution to a simple problem. Does anyone have an idea for a simpler solution?

What is actually the reason you are doing this?
Maybe instead of switching the views/models you could create multiple tabs and let the user switch the tabs?

···

On Tuesday, March 19, 2019 at 10:49:32 AM UTC+1, Sebastian Meinhardt wrote:

Interesting. That certainly solved my problem, thanks. It also gave me an idea how to dig a bit deeper: I found that resetting the start and end properties of the x_range to None also does the trick. Replacing the range with a new object is not required. That is neat, if you have more complex requirements for your axes that you do not want to save and restore all the time (as I do in my original application).

I also noticed, that the y_axis in my example suffers the same problem with the same solution. That was to be expected.

That leaves me with the problem that my original model is of course a little more complicated than this little example I gave here. Keeping track of all axes of all plots and whether they are visible or not does not sound like a good solution to me. So, what is a good solution here? I can recurse through the model that I switch to, searching for all figure models, resetting their respective ranges (and not forgetting about extra_x_ranges and extra_y_ranges!):

def reset_ranges(model):
def reset(range):
range.start = None
range.end = None

if hasattr(model, 'children'):
    for child in model.children:
        reset_ranges(child)
if isinstance(model, bokeh.plotting.Figure):
    reset(model.x_range)
    reset(model.y_range)
    for _, item in model.extra_x_ranges.items():
        reset(item)
    for _, item in model.extra_y_ranges.items():
        reset(item)

``

I used this code on my original app and it worked. But it strikes me as a convoluted solution to a simple problem. Does anyone have an idea for a simpler solution?

The underlying data set is organized as a tree, where each leaf is small subset that can be plotted. The tree is very wide (though not deep) and the branch structure is very important when exploring the data set. At first, I dismissed tabs completely because the linear arrangement of tabs cannot properly map the tree structure. Maybe I should think about this again, just to be sure I am not missing something crucial. Tabs within Tabs maybe? Or use color to highlight the underlying structure? The wideness of the data set is an impeding factor here but I might find a solution by ordering the large number of tabs by importance.

The current solution…works. While I am still looking for a better solution, that is enough to continue development for now. I will come back to the high-level presentation after finishing the other features. My sub-plots will not care if they are shown in tabs or on my current switching-pages, thus I do not expect any trouble if I change my mind later.