Skip to content

Commit

Permalink
Allow subcoordinates to be drawn from NdOverlay dimensions (#6209)
Browse files Browse the repository at this point in the history
  • Loading branch information
philippjfr authored Jun 7, 2024
1 parent 346be14 commit 4bd31bc
Show file tree
Hide file tree
Showing 2 changed files with 48 additions and 12 deletions.
32 changes: 22 additions & 10 deletions holoviews/plotting/bokeh/element.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,9 @@
)
from bokeh.models.tools import Tool

from ...core import CompositeOverlay, Dataset, Dimension, DynamicMap, Element, util
from ...core import Dataset, Dimension, DynamicMap, Element, util
from ...core.options import Keywords, SkipRendering, abbreviated_exception
from ...core.overlay import CompositeOverlay, NdOverlay
from ...element import Annotation, Contours, Graph, Path, Tiles, VectorField
from ...streams import Buffer, PlotSize, RangeXY
from ...util.transform import dim
Expand Down Expand Up @@ -831,7 +832,7 @@ def _create_extra_axes(self, plots, subplots, element, ranges):

ax_specs, yaxes, dimensions = {}, {}, {}
subcoordinate_axes = 0
for el, sp in zip(element, self.subplots.values()):
for el, (sp_key, sp) in zip(element, self.subplots.items()):
ax_dims = sp._get_axis_dims(el)[:2]
if sp.invert_axes:
ax_dims[::-1]
Expand All @@ -842,7 +843,10 @@ def _create_extra_axes(self, plots, subplots, element, ranges):
if self._subcoord_overlaid:
if opts.get('subcoordinate_y') is None:
continue
ax_name = el.label
if element.kdims:
ax_name = ', '.join(d.pprint_value(k) for d, k in zip(element.kdims, sp_key))
else:
ax_name = el.label
subcoordinate_axes += 1
else:
ax_name = yd.name
Expand Down Expand Up @@ -1129,13 +1133,16 @@ def _axis_properties(self, axis, key, plot, dimension=None,
elif not self.drawn:
ticks, labels = [], []
idx = 0
for el, sp in zip(self.current_frame, self.subplots.values()):
for el, (sp_key, sp) in zip(self.current_frame, self.subplots.items()):
if not sp.subcoordinate_y:
continue
ycenter = idx if isinstance(sp.subcoordinate_y, bool) else 0.5 * sum(sp.subcoordinate_y)
idx += 1
ticks.append(ycenter)
labels.append(el.label)
if el.label or not self.current_frame.kdims:
labels.append(el.label)
else:
labels.append(', '.join(d.pprint_value(k) for d, k in zip(self.current_frame.kdims, sp_key)))
axis_props['ticker'] = FixedTicker(ticks=ticks)
if labels is not None:
axis_props['major_label_overrides'] = dict(zip(ticks, labels))
Expand Down Expand Up @@ -2109,6 +2116,9 @@ def initialize_plot(self, ranges=None, plot=None, plots=None, source=None):
if style_element.label in plot.extra_y_ranges:
self.handles['subcoordinate_y_range'] = plot.y_range
self.handles['y_range'] = plot.extra_y_ranges.pop(style_element.label)
elif self.overlay_dims:
key = ', '.join(d.pprint_value(v) for d, v in self.overlay_dims.items())
self.handles['y_range'] = plot.extra_y_ranges.pop(key)

if self.apply_hard_bounds:
self._apply_hard_bounds(element, ranges)
Expand Down Expand Up @@ -3287,12 +3297,14 @@ def initialize_plot(self, ranges=None, plot=None, plots=None):
labels = self.hmap.last.traverse(lambda x: x.label, [
lambda el: isinstance(el, Element) and el.opts.get('plot').kwargs.get('subcoordinate_y', False)
])
if any(not label for label in labels):
if isinstance(self.hmap.last, NdOverlay):
pass
elif any(not label for label in labels):
raise ValueError(
'Every element wrapped in a subcoordinate_y overlay must have '
'a label.'
'Every Element plotted on a subcoordinate_y axis must have '
'a label or be part of an NdOverlay.'
)
if len(set(labels)) == 1:
elif len(set(labels)) != len(labels):
raise ValueError(
'Elements wrapped in a subcoordinate_y overlay must all have '
'a unique label.'
Expand Down Expand Up @@ -3339,7 +3351,7 @@ def initialize_plot(self, ranges=None, plot=None, plots=None):
):
subcoord_y_glyph_renderers.append(glyph_renderer)

if self.subcoordinate_y:
if self.subcoordinate_y and plot:
# Reverse the subcoord-y renderers only.
reversed_renderers = subcoord_y_glyph_renderers[::-1]
reordered = []
Expand Down
28 changes: 26 additions & 2 deletions holoviews/tests/plotting/bokeh/test_subcoordy.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import pytest
from bokeh.models.tools import WheelZoomTool, ZoomInTool, ZoomOutTool

from holoviews.core import Overlay
from holoviews.core import NdOverlay, Overlay
from holoviews.element import Curve
from holoviews.element.annotation import VSpan
from holoviews.operation.normalization import subcoordinate_group_ranges
Expand Down Expand Up @@ -76,11 +76,35 @@ def test_bool_scale(self):
assert plot.handles['y_range'].start == ytarget[0]
assert plot.handles['y_range'].end == ytarget[1]

def test_ndoverlay_labels(self):
overlay = NdOverlay({
f'Data {i}': Curve(np.arange(10)*i).opts(subcoordinate_y=True)
for i in range(3)
}, 'Channel')
plot = bokeh_renderer.get_plot(overlay)
assert plot.state.yaxis.ticker.ticks == [0, 1, 2]
assert plot.state.yaxis.major_label_overrides == {0: 'Data 0', 1: 'Data 1', 2: 'Data 2'}
for i, sp in enumerate(plot.subplots.values()):
assert sp.handles['glyph_renderer'].coordinates.y_target.start == (i-0.5)
assert sp.handles['glyph_renderer'].coordinates.y_target.end == (i+0.5)

def test_ndoverlay_nd_labels(self):
overlay = NdOverlay({
('A', f'Data {i}'): Curve(np.arange(10)*i).opts(subcoordinate_y=True)
for i in range(3)
}, ['Group', 'Channel'])
plot = bokeh_renderer.get_plot(overlay)
assert plot.state.yaxis.ticker.ticks == [0, 1, 2]
assert plot.state.yaxis.major_label_overrides == {0: 'A, Data 0', 1: 'A, Data 1', 2: 'A, Data 2'}
for i, sp in enumerate(plot.subplots.values()):
assert sp.handles['glyph_renderer'].coordinates.y_target.start == (i-0.5)
assert sp.handles['glyph_renderer'].coordinates.y_target.end == (i+0.5)

def test_no_label(self):
overlay = Overlay([Curve(range(10)).opts(subcoordinate_y=True) for i in range(2)])
with pytest.raises(
ValueError,
match='Every element wrapped in a subcoordinate_y overlay must have a label'
match='Every Element plotted on a subcoordinate_y axis must have a label or be part of an NdOverlay.'
):
bokeh_renderer.get_plot(overlay)

Expand Down

0 comments on commit 4bd31bc

Please sign in to comment.