Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Apply autorange on data change events #5609

Merged
merged 6 commits into from
Feb 6, 2023
Merged
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
philippjfr marked this conversation as resolved.
Show resolved Hide resolved
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