Skip to content
Open
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
22 changes: 20 additions & 2 deletions holoviews/selection.py
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,8 @@ def instance(self_or_cls, **params):
inst._obj_selections = {}
inst._obj_regions = {}
inst._reset_regions = True
inst._user_show_regions = inst.show_regions
inst._updating_show_regions_internal = False

# _datasets caches
inst._datasets = []
Expand All @@ -298,6 +300,12 @@ def instance(self_or_cls, **params):

return inst

@param.depends('show_regions', watch=True)
def _update_user_show_regions(self):
if self._updating_show_regions_internal:
return
self._user_show_regions = self.show_regions

@param.depends('selection_expr', watch=True)
def _update_pipes(self):
sel_expr = self.selection_expr
Expand Down Expand Up @@ -392,10 +400,17 @@ def update_selection_expr(*_):
new_selection_expr = inst.selection_expr
current_selection_expr = inst._cross_filter_stream.selection_expr
if repr(new_selection_expr) != repr(current_selection_expr):
# Reset the streams
for s in inst._selection_expr_streams.values():
s.reset()
s.event()
# Disable regions if setting selection_expr directly
if inst.show_regions:
if inst._user_show_regions:
inst._updating_show_regions_internal = True
inst.show_regions = False
inst._updating_show_regions_internal = False
inst._selection_override.event(selection_expr=new_selection_expr)
inst._cross_filter_stream.selection_expr = new_selection_expr
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's what fixes #6688


inst.param.watch(
update_selection_expr, ['selection_expr']
Expand All @@ -405,6 +420,10 @@ def selection_expr_changed(*_):
new_selection_expr = inst._cross_filter_stream.selection_expr
if repr(inst.selection_expr) != repr(new_selection_expr):
inst.selection_expr = new_selection_expr
if inst._user_show_regions:
inst._updating_show_regions_internal = True
inst.show_regions = True
inst._updating_show_regions_internal = False

inst._cross_filter_stream.param.watch(
selection_expr_changed, ['selection_expr']
Expand All @@ -415,7 +434,6 @@ def selection_expr_changed(*_):
def clear_stream_history(resetting, stream=stream):
if resetting:
stream.clear_history()
print("registering reset for ", stream)
stream.plot_reset_stream.param.watch(
clear_stream_history, ['resetting']
)
Expand Down
14 changes: 14 additions & 0 deletions holoviews/tests/test_selection.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,20 @@ def test_layout_selection_points_table(self):
]
)

def test_select_expr_show_regions(self):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we need a UI test for this change.

lnk_sel = link_selections.instance()
self.assertTrue(lnk_sel.show_regions)
se = (
(hv.dim('x') >= 0) & (hv.dim('x') <= 1) &
(hv.dim('y') >= 0) & (hv.dim('y') <= 1)
)
lnk_sel.selection_expr = se
self.assertFalse(lnk_sel.show_regions)
lnk_sel.selection_expr = None
self.assertFalse(lnk_sel.show_regions)
lnk_sel._cross_filter_stream.selection_expr = se
self.assertTrue(lnk_sel.show_regions)

def test_overlay_points_errorbars(self, dynamic=False):
points = Points(self.data)
error = ErrorBars(self.data, kdims='x', vdims=['y', 'e'])
Expand Down
64 changes: 64 additions & 0 deletions holoviews/tests/ui/bokeh/test_link_selections.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@

import pytest

import holoviews as hv
from holoviews.plotting.bokeh.renderer import BokehRenderer
from holoviews.selection import link_selections

from .. import expect

pytestmark = pytest.mark.ui


@pytest.mark.usefixtures("bokeh_backend")
def test_link_selections_programmatic_clear_removes_region(serve_hv):
points = hv.Points([1, 2, 3, 4, 5, 6, 7, 8]).opts(width=400, height=300)

# Helper to inspect bokeh model renderers for region-like glyphs
def count_highlighted_region_renderers(bokeh_plot):
cnt = 0
for r in bokeh_plot.renderers:
data = r.data_source.data
if len(data.get("left", [])) > 0:
cnt += 1
continue
return cnt

ls = link_selections.instance()
linked = ls(points).opts(active_tools=["box_select"])

page = serve_hv(linked)
hv_plot = page.locator(".bk-events")
expect(hv_plot).to_have_count(1)
bbox = hv_plot.bounding_box()

page.wait_for_timeout(300)
initial_count = count_highlighted_region_renderers(BokehRenderer.get_plot(linked[()]).state)
assert initial_count == 0

# Box-drag selection
start_x = bbox["x"] + bbox["width"] * 0.25
start_y = bbox["y"] + bbox["height"] * 0.25
end_x = bbox["x"] + bbox["width"] * 0.75
end_y = bbox["y"] + bbox["height"] * 0.75

hv_plot.click()
page.mouse.move(start_x, start_y)
page.mouse.down(button="left")
page.mouse.move(end_x, end_y, steps=10)
page.mouse.up(button="left")

page.wait_for_timeout(200)

assert ls.selection_expr is not None

# Ensure at least one new region renderer was created by the interaction
post_select_count = count_highlighted_region_renderers(BokehRenderer.get_plot(linked[()]).state)
assert post_select_count == initial_count + 1

ls.selection_expr = None
page.wait_for_timeout(200)

# Final region count should be back to initial
final_count = count_highlighted_region_renderers(BokehRenderer.get_plot(linked[()]).state)
assert final_count == initial_count
Loading