Skip to content

Commit

Permalink
Add preprocessing hook to link HoloViews plot axes across panel layout (
Browse files Browse the repository at this point in the history
  • Loading branch information
philippjfr authored Aug 11, 2019
1 parent d01735c commit efb340e
Show file tree
Hide file tree
Showing 2 changed files with 128 additions and 1 deletion.
47 changes: 47 additions & 0 deletions panel/pane/holoviews.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ class HoloViews(PaneBase):
center = param.Boolean(default=False, doc="""
Whether to center the plot.""")

linked_axes = param.Boolean(default=True, doc="""
Whether to use link the axes of bokeh plots inside this pane
across a panel layout.""")

widget_location = param.ObjectSelector(default='right_top', objects=[
'left', 'bottom', 'right', 'top', 'top_left', 'top_right',
'bottom_left', 'bottom_right', 'left_top', 'left_bottom',
Expand Down Expand Up @@ -409,4 +413,47 @@ def find_links(root_view, root_model):
return callbacks


def link_axes(root_view, root_model):
"""
Pre-processing hook to allow linking axes across HoloViews bokeh
plots.
"""
panes = root_view.select(HoloViews)

if not panes:
return

from holoviews.core.options import Store
from holoviews.plotting.bokeh.element import ElementPlot

ref = root_model.ref['id']
range_map = defaultdict(list)
for pane in panes:
if ref not in pane._plots:
continue
plot = pane._plots[ref][0]
if not pane.linked_axes or plot.renderer.backend != 'bokeh':
continue
for p in plot.traverse(specs=[ElementPlot]):
axiswise = Store.lookup_options('bokeh', p.current_frame, 'norm').kwargs.get('axiswise')
if not p.shared_axes or axiswise:
continue

fig = p.state
if fig.x_range.tags:
range_map[fig.x_range.tags[0]].append((fig, p, fig.x_range))
if fig.y_range.tags:
range_map[fig.y_range.tags[0]].append((fig, p, fig.y_range))

for tag, axes in range_map.items():
fig, p, axis = axes[0]
for fig, p, _ in axes[1:]:
if tag in fig.x_range.tags and not axis is fig.x_range:
fig.x_range = axis
p.handles['x_range'] = axis
if tag in fig.y_range.tags and not axis is fig.y_range:
fig.y_range = axis
p.handles['y_range'] = axis

Viewable._preprocessing_hooks.append(link_axes)
Viewable._preprocessing_hooks.append(find_links)
82 changes: 81 additions & 1 deletion panel/tests/pane/test_holoviews.py
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,7 @@ def test_holoviews_layouts(document, comm):
hv_pane = HoloViews(hmap, backend='bokeh')
layout = hv_pane.layout
model = layout.get_root(document, comm)

for center in (True, False):
for loc in HoloViews.param.widget_location.objects:
hv_pane.set_param(center=center, widget_location=loc)
Expand Down Expand Up @@ -386,6 +386,86 @@ def test_holoviews_widgets_explicit_widget_instance_override():
assert widgets[0] is widget


@hv_available
def test_holoviews_linked_axes(document, comm):
c1 = hv.Curve([1, 2, 3])
c2 = hv.Curve([1, 2, 3])

layout = Row(HoloViews(c1, backend='bokeh'), HoloViews(c2, backend='bokeh'))

row_model = layout.get_root(document, comm=comm)

print(row_model.children)

p1, p2 = row_model.select({'type': Figure})

assert p1.x_range is p2.x_range
assert p1.y_range is p2.y_range


@hv_available
def test_holoviews_linked_x_axis(document, comm):
c1 = hv.Curve([1, 2, 3])
c2 = hv.Curve([1, 2, 3], vdims='y2')

layout = Row(HoloViews(c1, backend='bokeh'), HoloViews(c2, backend='bokeh'))

row_model = layout.get_root(document, comm=comm)

p1, p2 = row_model.select({'type': Figure})

assert p1.x_range is p2.x_range
assert p1.y_range is not p2.y_range


@hv_available
def test_holoviews_axiswise_not_linked_axes(document, comm):
c1 = hv.Curve([1, 2, 3])
c2 = hv.Curve([1, 2, 3]).opts(axiswise=True, backend='bokeh')

layout = Row(HoloViews(c1, backend='bokeh'), HoloViews(c2, backend='bokeh'))

row_model = layout.get_root(document, comm=comm)

p1, p2 = row_model.select({'type': Figure})

assert p1.x_range is not p2.x_range
assert p1.y_range is not p2.y_range


@hv_available
def test_holoviews_shared_axes_opt_not_linked_axes(document, comm):
c1 = hv.Curve([1, 2, 3])
c2 = hv.Curve([1, 2, 3]).opts(shared_axes=False, backend='bokeh')

layout = Row(HoloViews(c1, backend='bokeh'), HoloViews(c2, backend='bokeh'))

row_model = layout.get_root(document, comm=comm)

p1, p2 = row_model.select({'type': Figure})

assert p1.x_range is not p2.x_range
assert p1.y_range is not p2.y_range


@hv_available
def test_holoviews_not_linked_axes(document, comm):
c1 = hv.Curve([1, 2, 3])
c2 = hv.Curve([1, 2, 3])

layout = Row(
HoloViews(c1, backend='bokeh'),
HoloViews(c2, backend='bokeh', linked_axes=False)
)

row_model = layout.get_root(document, comm=comm)

p1, p2 = row_model.select({'type': Figure})

assert p1.x_range is not p2.x_range
assert p1.y_range is not p2.y_range


@hv_available
def test_holoviews_link_across_panes(document, comm):
from bokeh.models.tools import RangeTool
Expand Down

0 comments on commit efb340e

Please sign in to comment.