Skip to content
Merged
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
3 changes: 2 additions & 1 deletion examples/selection_tools/linear_selector.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
Linear Selectors
================

Example showing how to use a `LinearSelector` with lines and line collections.
Example showing how to use a `LinearSelector` with lines and line collections. The linear selector is the yellow
vertical line.
"""

# test_example = true
Expand Down
9 changes: 5 additions & 4 deletions examples/selection_tools/linear_selector_image.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@
Linear Selectors Image
======================

Example showing how to use a `LinearSelector` to selector rows or columns of an image. The subplot on the right
displays the data for the selector row and column.
Example showing how to use a `LinearSelector` to select rows or columns of an image. The subplot on the right
displays the data for the selector row and column. Move the selectors independently or click the middle mouse
button to move both selectors to the clicked location.
"""

# test_example = false
Expand All @@ -24,10 +25,10 @@
image = figure[0, 0].add_image(image_data)

# add a row selector
image_row_selector = image.add_linear_selector(axis="y")
image_row_selector = image.add_linear_selector(axis="y", edge_color="cyan")

# add column selector
image_col_selector = image.add_linear_selector()
image_col_selector = image.add_linear_selector(edge_color="cyan")

# make a line to indicate row data
line_image_row = figure[0, 1].add_line(image.data[0])
Expand Down
3 changes: 3 additions & 0 deletions examples/selection_tools/unit_circle.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,9 @@ def set_x_val(ev):
sine_selector.add_event_handler(set_x_val, "selection")
cosine_selector.add_event_handler(set_x_val, "selection")

# set initial position of the selector so it's not just overlapping the y-axis
sine_selector.selection = 100

figure.show()


Expand Down
18 changes: 9 additions & 9 deletions fastplotlib/graphics/features/_selection_features.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,9 @@ def set_value(self, selector, value: float):
elif self._axis == "y":
dim = 1

for edge in selector._edges:
edge.geometry.positions.data[:, dim] = value
edge.geometry.positions.update_range()
edge = selector._edges[0]
edge.geometry.positions.data[:, dim] = value
edge.geometry.positions.update_range()

self._value = value

Expand Down Expand Up @@ -152,10 +152,10 @@ def set_value(self, selector, value: Sequence[float]):
selector.fill.geometry.positions.data[mesh_masks.x_right] = value[1]

# change x position of the left edge line
selector.edges[0].geometry.positions.data[:, 0] = value[0]
selector._edges[0].geometry.positions.data[:, 0] = value[0]

# change x position of the right edge line
selector.edges[1].geometry.positions.data[:, 0] = value[1]
selector._edges[1].geometry.positions.data[:, 0] = value[1]

elif self.axis == "y":
# change bottom y position of the fill mesh
Expand All @@ -165,18 +165,18 @@ def set_value(self, selector, value: Sequence[float]):
selector.fill.geometry.positions.data[mesh_masks.y_top] = value[1]

# change y position of the bottom edge line
selector.edges[0].geometry.positions.data[:, 1] = value[0]
selector._edges[0].geometry.positions.data[:, 1] = value[0]

# change y position of the top edge line
selector.edges[1].geometry.positions.data[:, 1] = value[1]
selector._edges[1].geometry.positions.data[:, 1] = value[1]

self._value = value

# send changes to GPU
selector.fill.geometry.positions.update_range()

selector.edges[0].geometry.positions.update_range()
selector.edges[1].geometry.positions.update_range()
selector._edges[0].geometry.positions.update_range()
selector._edges[1].geometry.positions.update_range()

# send event
if len(self._event_handlers) < 1:
Expand Down
31 changes: 26 additions & 5 deletions fastplotlib/graphics/selectors/_base_selector.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ def edge_color(self, color: str | Sequence[float]):
def __init__(
self,
edges: Tuple[Line, ...] = None,
outer_edges: Tuple[Line, ...] = None,
fill: Tuple[Mesh, ...] = None,
vertices: Tuple[Points, ...] = None,
hover_responsive: Tuple[WorldObject, ...] = None,
Expand All @@ -122,18 +123,25 @@ def __init__(
if edges is None:
edges = tuple()

if outer_edges is None:
outer_edges = tuple()

if fill is None:
fill = tuple()

if vertices is None:
vertices = tuple()

self._edges: Tuple[Line, ...] = edges
self._outer_edges: Tuple[Line, ...] = outer_edges
self._fill: Tuple[Mesh, ...] = fill
self._vertices: Tuple[Points, ...] = vertices

self._world_objects: Tuple[WorldObject, ...] = (
self._edges + self._fill + self._vertices
*self._edges,
*self._outer_edges,
*self._fill,
*self._vertices,
)

for wo in self._world_objects:
Expand All @@ -148,7 +156,7 @@ def __init__(
self._hover_colors = {}

if hover_responsive is not None:
for wo in self._hover_responsive:
for wo in [*self._hover_responsive, *self._outer_edges]:
self._original_colors[wo] = wo.material.color

self._axis = axis
Expand Down Expand Up @@ -231,7 +239,7 @@ def _fpl_add_plot_area_hook(self, plot_area):
self._plot_area.renderer.add_event_handler(self._move_to_pointer, "click")

# mouse hover color events
for wo in self._hover_responsive:
for wo in [*self._hover_responsive, *self._outer_edges]:
wo.add_event_handler(self._pointer_enter, "pointer_enter")
wo.add_event_handler(self._pointer_leave, "pointer_leave")

Expand Down Expand Up @@ -282,6 +290,12 @@ def _move_start(self, event_source: WorldObject, ev):
"""
position = self._plot_area.map_screen_to_world(ev)

# if the event source was an outer transparent line, get the
# corresponding inner line since it's just a proxy
if event_source in self._outer_edges:
index = self._outer_edges.index(event_source)
event_source = self._edges[index]

self._move_info = MoveInfo(
start_selection=None,
start_position=position,
Expand Down Expand Up @@ -397,9 +411,16 @@ def _pointer_enter(self, ev):
return

wo = ev.pick_info["world_object"]
if wo not in self._hover_responsive:
if wo not in [*self._hover_responsive, *self._outer_edges]:
return

# if it's an outer edge, highlight the corresponding inner edge instead
if wo in self._outer_edges:
# get index
index = self._outer_edges.index(wo)
# now use inner edge
wo = self._edges[index]

if wo in self._edges:
self._edge_hovered = True

Expand All @@ -415,7 +436,7 @@ def _pointer_leave(self, ev):
self._edge_hovered = False

# reset colors
for wo in self._hover_responsive:
for wo in [*self._hover_responsive, *self._outer_edges]:
if self._moving:
self._hover_colors[wo] = self._original_colors[wo]
else:
Expand Down
26 changes: 15 additions & 11 deletions fastplotlib/graphics/selectors/_linear.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,9 +78,10 @@ def __init__(
limits: Sequence[float],
axis: str = "x",
parent: Graphic = None,
edge_color: str | Sequence[float] | np.ndarray = "w",
thickness: float = 2.5,
edge_color: str | Sequence[float] | np.ndarray = "yellow",
thickness: float = 1.0,
arrow_keys_modifier: str = "Shift",
extra_width: float = 14.0,
name: str = None,
):
"""
Expand Down Expand Up @@ -111,6 +112,9 @@ def __init__(
edge_color: str | tuple | np.ndarray, default "w"
color of the selector

extra_width: float, default 14.0
the width around the selector which is responsive to mouse events, in logical pixels

name: str, optional
name of linear selector

Expand Down Expand Up @@ -141,8 +145,6 @@ def __init__(

material = pygfx.LineInfiniteSegmentMaterial

self.colors_outer = pygfx.Color([0.3, 0.3, 0.3, 1.0])

line_inner = pygfx.Line(
# self.data.feature_data because data is a Buffer
geometry=pygfx.Geometry(positions=line_data),
Expand All @@ -158,11 +160,12 @@ def __init__(
),
)

self.line_outer = pygfx.Line(
geometry=pygfx.Geometry(positions=line_data),
line_outer = pygfx.Line(
geometry=line_inner.geometry,
material=material(
thickness=thickness + 6,
color=self.colors_outer,
thickness=thickness + extra_width,
color=pygfx.Color([0, 0, 0]),
opacity=0,
alpha_mode="blend",
aa=True,
render_queue=RenderQueue.selector,
Expand All @@ -177,7 +180,7 @@ def __init__(

world_object = pygfx.Group()

world_object.add(self.line_outer)
world_object.add(line_outer)
world_object.add(line_inner)

if axis == "x":
Expand All @@ -188,8 +191,9 @@ def __init__(
# init base selector
BaseSelector.__init__(
self,
edges=(line_inner, self.line_outer),
hover_responsive=(line_inner, self.line_outer),
edges=(line_inner,),
outer_edges=(line_outer,),
hover_responsive=(line_inner,),
arrow_keys_modifier=arrow_keys_modifier,
axis=axis,
parent=parent,
Expand Down
59 changes: 51 additions & 8 deletions fastplotlib/graphics/selectors/_linear_region.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,10 @@ def __init__(
parent: Graphic = None,
resizable: bool = True,
fill_color: str | Sequence[float] = (0, 0, 0.35),
edge_color: str | Sequence[float] = (0.8, 0.6, 0),
edge_thickness: float = 8,
edge_color: str | Sequence[float] = "yellow",
edge_thickness: float = 1.0,
arrow_keys_modifier: str = "Shift",
extra_width: float = 14.0,
name: str = None,
):
"""
Expand Down Expand Up @@ -113,6 +114,9 @@ def __init__(
modifier key that must be pressed to initiate movement using arrow keys, must be one of:
"Control", "Shift", "Alt" or ``None``

extra_width: float, default 14.0
the width around the selector lines which is responsive to mouse events, in logical pixels

name: str, optional
name of this selector graphic

Expand Down Expand Up @@ -215,6 +219,25 @@ def __init__(
pick_write=True,
),
)

line0_outer = pygfx.Line(
pygfx.Geometry(
# share buffer with inner line so they can both be managed together
positions=line0.geometry.positions
),
pygfx.LineMaterial(
thickness=edge_thickness + extra_width,
color=pygfx.Color([0, 0, 0]),
alpha_mode="blend",
opacity=0,
aa=True,
render_queue=RenderQueue.selector,
depth_test=False,
depth_write=False,
pick_write=True,
),
)

line1 = pygfx.Line(
pygfx.Geometry(
positions=init_line_data.copy()
Expand All @@ -232,8 +255,27 @@ def __init__(
),
)

self.edges: tuple[pygfx.Line, pygfx.Line] = (line0, line1)
group.add(*self.edges)
line1_outer = pygfx.Line(
pygfx.Geometry(
# share buffer with inner line so they can both be managed together
positions=line1.geometry.positions
),
pygfx.LineMaterial(
thickness=edge_thickness + extra_width,
color=pygfx.Color([0, 0, 0]),
alpha_mode="blend",
opacity=0,
aa=True,
render_queue=RenderQueue.selector,
depth_test=False,
depth_write=False,
pick_write=True,
),
)

edges: tuple[pygfx.Line, pygfx.Line] = (line0, line1)
outer_edges = (line0_outer, line1_outer)
group.add(*edges, *outer_edges)

# TODO: if parent offset changes, we should set the selector offset too, use offset evented property
# TODO: add check if parent is `None`, will throw error otherwise
Expand All @@ -253,9 +295,10 @@ def __init__(

BaseSelector.__init__(
self,
edges=self.edges,
edges=edges,
outer_edges=outer_edges,
fill=(self.fill,),
hover_responsive=self.edges,
hover_responsive=edges,
arrow_keys_modifier=arrow_keys_modifier,
axis=axis,
parent=parent,
Expand Down Expand Up @@ -423,12 +466,12 @@ def _move_graphic(self, move_info: MoveInfo):

# if event source was an edge and selector is resizable,
# move the edge that caused the event
if move_info.source == self.edges[0]:
if move_info.source == self._edges[0]:
# change only left or bottom bound
new_min = min(cur_min + delta, cur_max)
self._selection.set_value(self, (new_min, cur_max))

elif move_info.source == self.edges[1]:
elif move_info.source == self._edges[1]:
# change only right or top bound
new_max = max(cur_max + delta, cur_min)
self._selection.set_value(self, (cur_min, new_max))
1 change: 0 additions & 1 deletion fastplotlib/tools/_histogram_lut.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,6 @@ def __init__(
size=size,
center=origin[0],
axis="y",
edge_thickness=8,
parent=self._histogram_line,
)

Expand Down
Loading