-
-
Notifications
You must be signed in to change notification settings - Fork 4.2k
Description
Software versions
Python version : 3.12.11 (main, Jun 12 2025, 12:44:17) [MSC v.1943 64 bit (AMD64)]
IPython version : (not installed)
Tornado version : 6.5.1
NumPy version : 1.26.4
Bokeh version : 3.7.3
BokehJS static path : C:\Users\eolfat\OneDrive - Echodyne\Desktop\sample data\.venv\Lib\site-packages\bokeh\server\static
node.js version : (not installed)
npm version : (not installed)
jupyter_bokeh version : (not installed)
Operating system : Windows-11-10.0.26100-SP0
Browser name and version
No response
Jupyter notebook / Jupyter Lab version
No response
Expected behavior
In my original code, there's an instance where I have to recreate my map figure and reapply the ranges it had prior to recreating it. This is because the user might have zoomed-in on the plot, and I want the recreated figure to show the same zoomed-in view, while still allowing the user to reset these ranges using Bokeh's reset tool (reset back to whatever ranges the figure had before zooming). My figure is created with Mercator type axes, tiles, and defined ranges.
If you look at the example code provided here: On both plots (No tile, and With tile), zooming/panning then clicking the Recreate button, then clicking the reset tool from the bokeh toolbar should reset the ranges to the original ranges (the view it started with).
Observed behavior
I realized while this series of actions (zoom -> recreate -> reset tool) works as expected on the figures without a tile provider, it does not reset to the original ranges on the plot with tile provider.
Looking at the example code I provided, the "No tile" plot works as expected. Meaning after zooming then clicking Recreate, the zoom range remains preserved with whatever way it was prior to clicking the Recreate button, and clicking the reset tool from the bokeh toolbar properly resets the ranges to the original un-zoomed ranges.
However, this does not work when the plot figure is created with tiles. If you zoom in on the "With tile" plot and then hit Recreate, the ranges do remain preserved but clicking the reset tool won't reset it back to its original ranges. In fact, if you zoom in and out from that state, clicking the reset tool will bring the ranges back to whatever it was AFTER clicking the Recreate button. This means the user has no way of going back to the original un-zoomed ranges.
As the example code shows, the only difference between how the two plots are created is that one is created with tiles. I've noticed this issue happens when the figure is created with explicit x_range and y_range, and tile is added. If tile is added to a plot that does not have explicitly defined x_range and y_range upon creation, it no longer seems to have this issue.
Example code
from bokeh.plotting import figure, curdoc
from bokeh.models import Button, Column, RadioButtonGroup, ResetTool, PanTool, Row, WheelZoomTool
# Global state
current_fig = None
main_layout = None
def create_map(with_tile=False):
"""Create mercator map"""
fig = figure(
title="No tile",
tools=[PanTool(), WheelZoomTool(), ResetTool()],
x_axis_type="mercator",
y_axis_type="mercator",
x_range=(0, 5),
y_range=(0, 5),
)
if with_tile:
fig.title.text="With tile"
fig.add_tile("ESRI WorldGrayCanvas")
fig.scatter([1,2,3,4], [1,2,3,4], size=8, alpha=0.7, color='red')
return fig
def toggle_plot(attr, old, new):
global current_fig, main_layout
current_fig = create_map(new == 1)
main_layout.children[1] = current_fig
def recreate_plot():
global current_fig, main_layout
# Store ranges
zoom_ranges = {
"x_range": (current_fig.x_range),
"y_range": (current_fig.y_range),
}
# Recreate figure
if current_fig.title.text == "No tile":
current_fig = create_map()
else:
current_fig = create_map(True)
# Restore ranges
current_fig.x_range = zoom_ranges["x_range"]
current_fig.y_range = zoom_ranges["y_range"]
main_layout.children[1] = current_fig
# Create widgets
toggle_button = RadioButtonGroup(labels=["No tile", "With tile"], active=0)
toggle_button.on_change('active', toggle_plot)
recreate_button = Button(label="Recreate")
recreate_button.on_click(recreate_plot)
# Initialize
current_fig = create_map()
main_layout = Column(Row(toggle_button, recreate_button), current_fig)
curdoc().add_root(main_layout)
Stack traceback or browser console output
No response
Screenshots
No response