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

Add support for subcoordinate_group drawn from NdOverlay dimensions #6270

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all 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
73 changes: 52 additions & 21 deletions holoviews/plotting/bokeh/element.py
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,9 @@ class ElementPlot(BokehPlot, GenericElementPlot):
two-tuple that must be a range between 0 and 1, the plot will be
rendered on this vertical range of the axis.""")

subcoordinate_group = param.String(default=None, doc="""
Dimension in the data to group subcoordinates by.""")

subcoordinate_scale = param.Number(default=1, bounds=(0, None), inclusive_bounds=(False, True), doc="""
Scale factor for subcoordinate ranges to control the level of overlap.""")

Expand Down Expand Up @@ -1142,7 +1145,10 @@ def _axis_properties(self, axis, key, plot, dimension=None,
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)))
labels.append(', '.join(
d.pprint_value(k) for d, k in zip(self.current_frame.kdims, sp_key)
if d != self.subcoordinate_group
))
axis_props['ticker'] = FixedTicker(ticks=ticks)
if labels is not None:
axis_props['major_label_overrides'] = dict(zip(ticks, labels))
Expand Down Expand Up @@ -2982,7 +2988,7 @@ class OverlayPlot(GenericOverlayPlot, LegendPlot):
'min_height', 'max_height', 'min_width', 'min_height',
'margin', 'aspect', 'data_aspect', 'frame_width',
'frame_height', 'responsive', 'fontscale', 'subcoordinate_y',
'subcoordinate_scale', 'autorange']
'subcoordinate_scale', 'autorange', 'subcoordinate_group']

def __init__(self, overlay, **kwargs):
self._multi_y_propagation = self.lookup_options(overlay, 'plot').options.get('multi_y', False)
Expand Down Expand Up @@ -3195,27 +3201,36 @@ def _postprocess_subcoordinate_y_groups(self, overlay, plot):
# First, just process and validate the groups and their content.
groups = defaultdict(list)

# If there are groups AND there are subcoordinate_y elements without a group.
if any(el.group != type(el).__name__ for el in overlay) and any(
el.opts.get('plot').kwargs.get('subcoordinate_y', False)
and el.group == type(el).__name__
for el in overlay
):
raise ValueError(
'The subcoordinate_y overlay contains elements with a defined group, each '
'subcoordinate_y element in the overlay must have a defined group.'
)

for el in overlay:
missing_group = False
for sp in self.subplots.values():
# group is the Element type per default (e.g. Curve, Spike).
if el.group == type(el).__name__:
el = sp.current_frame
subcoord_enabled = el.opts.get('plot').kwargs.get('subcoordinate_y', False)
subcoord_group = sp.subcoordinate_group or self.subcoordinate_group
if sp.overlay_dims and subcoord_group:
coord_groups = [v for d, v in sp.overlay_dims.items() if d == subcoord_group]
group = coord_groups[0] if coord_groups else None
else:
group = None if el.group == type(el).__name__ else el.group

if group is None:
# If there are groups AND there are subcoordinate_y elements without a group.
if subcoord_enabled:
missing_group = True
continue
if not el.opts.get('plot').kwargs.get('subcoordinate_y', False):

if not subcoord_enabled:
raise ValueError(
f"All elements in group {el.group!r} must set the option "
f"All elements in group {group!r} must set the option "
f"'subcoordinate_y=True'. Not found for: {el}"
)
groups[el.group].append(el)
groups[group].append(el)

if groups and missing_group:
raise ValueError(
'The subcoordinate_y overlay contains elements with a defined group, each '
'subcoordinate_y element in the overlay must have a defined group.'
)

# No need to go any further if there's just one group.
if len(groups) <= 1:
Expand All @@ -3230,10 +3245,18 @@ def _postprocess_subcoordinate_y_groups(self, overlay, plot):
renderers_per_group = defaultdict(list)
# We loop through each overlay sub-elements and empty the list of
# renderers of the initial tool.
for el in overlay:
if el.group not in groups:
for sp in self.subplots.values():
el = sp.current_frame
subcoord_group = sp.subcoordinate_group or self.subcoordinate_group
if sp.overlay_dims and subcoord_group:
coord_groups = [v for d, v in sp.overlay_dims.items() if d == subcoord_group]
group = coord_groups[0] if coord_groups else None
else:
group = None if el.group == type(el).__name__ else el.group

if group is None or group not in groups:
continue
renderers_per_group[el.group].append(zoom_tool.renderers.pop(0))
renderers_per_group[group].append(zoom_tool.renderers.pop(0))

if zoom_tool.renderers:
raise RuntimeError(f'Found unexpected zoom renderers {zoom_tool.renderers}')
Expand Down Expand Up @@ -3290,6 +3313,12 @@ def _get_axis_dims(self, element):
return subplots[0]._get_axis_dims(element)
return super()._get_axis_dims(element)

def _create_subplot(self, key, obj, streams, ranges, **kwargs):
sp = super()._create_subplot(key, obj, streams, ranges, **kwargs)
if sp is not None and not sp.subcoordinate_group and self.subcoordinate_group:
sp.subcoordinate_group = self.subcoordinate_group
return sp

def initialize_plot(self, ranges=None, plot=None, plots=None):
if self.multi_y and self.subcoordinate_y:
raise ValueError('multi_y and subcoordinate_y are not supported together.')
Expand Down Expand Up @@ -3332,6 +3361,8 @@ def initialize_plot(self, ranges=None, plot=None, plots=None):
frame = None
if self.tabs:
subplot.overlaid = False
if subplot.subcoordinate_group is None and self.subcoordinate_group:
subplot.subcoordinate_group = self.subcoordinate_group
child = subplot.initialize_plot(ranges, plot, plots)
if isinstance(element, CompositeOverlay):
# Ensure that all subplots are in the same state
Expand Down
4 changes: 2 additions & 2 deletions holoviews/plotting/plot.py
Original file line number Diff line number Diff line change
Expand Up @@ -1839,13 +1839,13 @@ def _create_subplots(self, ranges):
"in the Overlay.")
return subplots

def _create_subplot(self, key, obj, streams, ranges):
def _create_subplot(self, key, obj, streams, ranges, **kwargs):
registry = Store.registry[self.renderer.backend]
ordering = util.layer_sort(self.hmap)
overlay_type = 1 if self.hmap.type == Overlay else 2
group_fn = lambda x: (x.type.__name__, x.last.group, x.last.label)

opts = {'overlaid': overlay_type}
opts = dict(kwargs, overlaid=overlay_type)
if self.hmap.type == Overlay:
style_key = (obj.type.__name__,) + key
if self.overlay_dims:
Expand Down
Loading