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

enh: Add Scalebar support for subcoordinate_y plots #6403

Merged
merged 8 commits into from
Oct 15, 2024
Merged
Show file tree
Hide file tree
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
38 changes: 31 additions & 7 deletions holoviews/plotting/bokeh/element.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@
bokeh32,
bokeh34,
bokeh35,
bokeh36,
cds_column_replace,
compute_layout_properties,
date_to_integer,
Expand Down Expand Up @@ -208,7 +209,7 @@ class ElementPlot(BokehPlot, GenericElementPlot):
The scalebar_unit is only used if scalebar is True.""")

scalebar_location = param.Selector(
default="bottom_right",
default=None,
objects=[
"top_left", "top_center", "top_right",
"center_left", "center_center", "center_right",
Expand All @@ -218,6 +219,8 @@ class ElementPlot(BokehPlot, GenericElementPlot):
doc="""
Location anchor for positioning scale bar.

Default to 'bottom_right', except if subcoordinate_y is True then it will default to 'right'.

The scalebar_location is only used if scalebar is True.""")

scalebar_label = param.String(
Expand Down Expand Up @@ -2407,10 +2410,14 @@ def _draw_scalebar(self, plot):
`scalebar_opts`, and `scalebar_unit` for more information.

Requires Bokeh 3.4

For scalebar on a subcoordinate_y plot Bokeh 3.6 is needed.
"""

if not bokeh34:
raise RuntimeError("Scalebar requires Bokeh >= 3.4.0")
elif not bokeh36 and self._subcoord_overlaid:
raise RuntimeError("Scalebar with subcoordinate_y requires Bokeh >= 3.6.0")
hoxbro marked this conversation as resolved.
Show resolved Hide resolved

from bokeh.models import Metric, ScaleBar

Expand All @@ -2421,17 +2428,34 @@ def _draw_scalebar(self, plot):
else:
base_unit = unit

_default_scalebar_opts = {"background_fill_alpha": 0.8}
opts = dict(_default_scalebar_opts, **self.scalebar_opts)
if self._subcoord_overlaid:
srange = plot.renderers[-1].coordinates.y_source
hoxbro marked this conversation as resolved.
Show resolved Hide resolved
orientation = "vertical"
# Integer is used for the location as `major_label_overrides` overrides the
# label with {0: labelA, 1: labelB}
location = (self.scalebar_location or "right", len(plot.renderers) - 1)
default_scalebar_opts = {
'bar_line_width': 3,
'label_location': 'left',
'background_fill_color': 'white',
'background_fill_alpha': 0.6,
'length_sizing': 'adaptive',
# Adding location so people can overwrite the default
'location': location,
}
else:
srange = plot.x_range if self.scalebar_range == "x" else plot.y_range
orientation = "horizontal" if self.scalebar_range == "x" else "vertical"
location = self.scalebar_location or "bottom_right"
default_scalebar_opts = {"background_fill_alpha": 0.8, "location": location}

scale_bar = ScaleBar(
range=plot.x_range if self.scalebar_range == "x" else plot.y_range,
orientation="horizontal" if self.scalebar_range == "x" else "vertical",
range=srange,
orientation=orientation,
unit=unit,
dimensional=Metric(base_unit=base_unit),
location=self.scalebar_location,
label=self.scalebar_label,
**opts,
**dict(default_scalebar_opts, **self.scalebar_opts),
)
self.handles['scalebar'] = scale_bar
plot.add_layout(scale_bar)
Expand Down
1 change: 1 addition & 0 deletions holoviews/plotting/bokeh/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
bokeh33 = bokeh_version >= Version("3.3")
bokeh34 = bokeh_version >= Version("3.4")
bokeh35 = bokeh_version >= Version("3.5")
bokeh36 = bokeh_version >= Version("3.6")

TOOL_TYPES = {
'pan': tools.PanTool,
Expand Down
27 changes: 26 additions & 1 deletion holoviews/tests/plotting/bokeh/test_elementplot.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
from holoviews.core import Dimension, DynamicMap, HoloMap, NdOverlay, Overlay
from holoviews.core.util import dt_to_int
from holoviews.element import Curve, HeatMap, Image, Labels, Scatter
from holoviews.plotting.bokeh.util import bokeh34
from holoviews.plotting.bokeh.util import bokeh34, bokeh36
from holoviews.plotting.util import process_cmap
from holoviews.streams import PointDraw, Stream
from holoviews.util import render
Expand Down Expand Up @@ -885,6 +885,31 @@ def test_scalebar_icon_multiple_overlay(self):
scalebar_icon = [tool for tool in toolbar.tools if tool.description == "Toggle ScaleBar"]
assert len(scalebar_icon) == 1

@pytest.mark.skipif(not bokeh36, reason="requires Bokeh >= 3.6")
@pytest.mark.parametrize("enabled1", [True, False])
@pytest.mark.parametrize("enabled2", [True, False])
@pytest.mark.parametrize("enabled3", [True, False])
def test_scalebar_with_subcoordinate_y(self, enabled1, enabled2, enabled3):
from bokeh.models import ScaleBar

enabled = [enabled1, enabled2, enabled3]
curve1 = Curve([1, 2, 3], label='c1').opts(scalebar=enabled1, subcoordinate_y=True)
curve2 = Curve([1, 2, 3], label='c2').opts(scalebar=enabled2, subcoordinate_y=True)
curve3 = Curve([1, 2, 3], label='c3').opts(scalebar=enabled3, subcoordinate_y=True)
curves = curve1 * curve2 * curve3

plot = bokeh_renderer.get_plot(curves).handles["plot"]
coordinates = [r.coordinates for r in plot.renderers][::-1]
sb = (c for c in plot.center if isinstance(c, ScaleBar))
scalebars = [next(sb) if e else None for e in enabled]
assert sum(map(bool, scalebars)) == sum(enabled)

for coordinate, scalebar, idx in zip(coordinates, scalebars, "123"):
assert coordinate.y_source.name == f"c{idx}"
if scalebar is None:
continue
assert coordinate.y_source is scalebar.range


class TestColorbarPlot(LoggingComparisonTestCase, TestBokehPlot):

Expand Down