Skip to content

Commit

Permalink
Apply autorange on data change events (#5609)
Browse files Browse the repository at this point in the history
Co-authored-by: jlstevens <[email protected]>
  • Loading branch information
philippjfr and jlstevens authored Feb 6, 2023
1 parent bdc9c5b commit c8744d6
Show file tree
Hide file tree
Showing 2 changed files with 47 additions and 16 deletions.
2 changes: 1 addition & 1 deletion examples/user_guide/Customizing_Plots.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -597,7 +597,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"Autoranging works analogously for the x-axis and also respects the padding setting."
"Autoranging works analogously for the x-axis and also respects the padding setting. In addition, autoranging is triggered when the plotted data is updated dynamically, as is common when building interactive visualizations with operations or `DynamicMap`s."
]
},
{
Expand Down
61 changes: 46 additions & 15 deletions holoviews/plotting/bokeh/element.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from bokeh.document.events import ModelChangedEvent
from bokeh.models import (
BinnedTicker, ColorBar, ColorMapper, CustomJS, EqHistColorMapper,
Legend, Renderer, Title, tools,
GlyphRenderer, Legend, Renderer, Title, tools,
)
from bokeh.models.axes import CategoricalAxis, DatetimeAxis
from bokeh.models.formatters import (
Expand Down Expand Up @@ -221,6 +221,7 @@ def __init__(self, element, plot=None, **params):

# Whether axes are shared between plots
self._shared = {'x': False, 'y': False}
self._js_on_data_callbacks = []

# Flag to check whether plot has been updated
self._updated = False
Expand Down Expand Up @@ -963,6 +964,9 @@ def _setup_autorange(self):
Sets up a callback which will iterate over available data
renderers and auto-range along one axis.
"""
if not isinstance(self, OverlayPlot) and not self.apply_ranges:
return

if self.autorange is None:
return
dim = self.autorange
Expand All @@ -985,7 +989,6 @@ def _setup_autorange(self):
else:
p0, p1 = self.padding, self.padding


if dim == 'x':
lower, upper = self.xlim
lower = None if (lower is None) or np.isnan(lower) else lower
Expand All @@ -997,10 +1000,13 @@ def _setup_autorange(self):
upper = None if (upper is None) or np.isnan(upper) else upper

# Clean this up in bokeh 3.0 using View.find_one API
self.state.js_on_event('rangesupdate', CustomJS(code=f"""
const ref = cb_obj.origin.id
callback = CustomJS(code=f"""
const cb = function() {{
const ref = plot.id
const find = (view) => {{
for (const sv of view.child_views) {{
let iterable = view.child_views === undefined ? [] : view.child_views
for (const sv of iterable) {{
if (sv.model.id == ref)
return sv
const obj = find(sv)
Expand All @@ -1010,22 +1016,29 @@ def _setup_autorange(self):
return null
}}
let plot_view = null;
for (const root of cb_obj.origin.document.roots()) {{
for (const root of plot.document.roots()) {{
const root_view = window.Bokeh.index[root.id]
if (root_view === undefined)
return
plot_view = find(root_view)
if (plot_view != null)
break
}}
if (plot_view == null)
return
let [vmin, vmax] = [Infinity, -Infinity]
for (const dr of cb_obj.origin.data_renderers) {{
const index = plot_view.renderer_view(dr).glyph_view.index.index
for (let pos = 0; pos < index._boxes.length - 4; pos += 4) {{
const [x0, y0, x1, y1] = index._boxes.slice(pos, pos+4)
if ({odim}0 > cb_obj.{odim}0 && {odim}1 < cb_obj.{odim}1) {{
vmin = Math.min(vmin, {dim}0)
vmax = Math.max(vmax, {dim}1)
for (const dr of plot.data_renderers) {{
const renderer = plot_view.renderer_view(dr)
const glyph_view = renderer.glyph_view
if (!renderer.glyph.model.tags.includes('no_apply_ranges')) {{
const index = glyph_view.index.index
for (let pos = 0; pos < index._boxes.length - 4; pos += 4) {{
const [x0, y0, x1, y1] = index._boxes.slice(pos, pos+4)
if ({odim}0 > plot.{odim}_range.start && {odim}1 < plot.{odim}_range.end) {{
vmin = Math.min(vmin, {dim}0)
vmax = Math.max(vmax, {dim}1)
}}
}}
}}
}}
Expand All @@ -1046,9 +1059,15 @@ def _setup_autorange(self):
}}
if (start_finite && end_finite) {{
cb_obj.origin.{dim}_range.setv({{start, end}})
plot.{dim}_range.setv({{start, end}})
}}
"""))
}}
// The plot changes will not propagate to the glyph until
// after the data change event has occurred.
setTimeout(cb, 0);
""", args={'plot': self.state})
self.state.js_on_event('rangesupdate', callback)
self._js_on_data_callbacks.append(callback)

def _categorize_data(self, data, cols, dims):
"""
Expand Down Expand Up @@ -1130,6 +1149,7 @@ def _init_glyph(self, plot, mapping, properties):
"""
Returns a Bokeh glyph object.
"""
mapping['tags'] = ['apply_ranges' if self.apply_ranges else 'no_apply_ranges']
properties = mpl_to_bokeh(properties)
plot_method = self._plot_methods.get('batched' if self.batched else 'single')
if isinstance(plot_method, tuple):
Expand Down Expand Up @@ -1509,12 +1529,21 @@ def initialize_plot(self, ranges=None, plot=None, plots=None, source=None):
if not self.overlaid:
self._set_active_tools(plot)
self._process_legend()
self._setup_data_callbacks(plot)
self._execute_hooks(element)

self.drawn = True

return plot

def _setup_data_callbacks(self, plot):
if not self._js_on_data_callbacks:
return
for renderer in plot.select({'type': GlyphRenderer}):
cds = renderer.data_source
for cb in self._js_on_data_callbacks:
if cb not in cds.js_property_callbacks.get('change:data', []):
cds.js_on_change('data', cb)

def _update_glyphs(self, element, ranges, style):
plot = self.handles['plot']
Expand Down Expand Up @@ -1623,6 +1652,7 @@ def update_frame(self, key, ranges=None, plot=None, element=None):
self._update_ranges(style_element, ranges)
self._update_plot(key, plot, style_element)
self._set_active_tools(plot)
self._setup_data_callbacks(plot)
self._updated = True

if 'hover' in self.handles:
Expand Down Expand Up @@ -2571,6 +2601,7 @@ def update_frame(self, key, ranges=None, element=None):
plot = self.handles['plot']
self._update_plot(key, plot, element)
self._set_active_tools(plot)
self._setup_data_callbacks(plot)

self._updated = True
self._process_legend(element)
Expand Down

0 comments on commit c8744d6

Please sign in to comment.