Thanks to visit codestin.com
Credit goes to github.com

Skip to content

more selector stuff #199

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

Merged
merged 5 commits into from
May 22, 2023
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
28 changes: 21 additions & 7 deletions fastplotlib/graphics/image.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,16 @@


class _ImageHeatmapSelectorsMixin:
def add_linear_selector(self, selection: int = None, padding: float = 50, **kwargs) -> LinearSelector:
def add_linear_selector(self, selection: int = None, padding: float = None, **kwargs) -> LinearSelector:
"""
Adds a linear selector.

Parameters
----------
selection: int
selection: int, optional
initial position of the selector

padding: float
padding: float, optional
pad the length of the selector

kwargs
Expand All @@ -35,6 +35,12 @@ def add_linear_selector(self, selection: int = None, padding: float = 50, **kwar

"""

# default padding is 15% the height or width of the image
if "axis" in kwargs.keys():
axis = kwargs["axis"]
else:
axis = "x"

bounds_init, limits, size, origin, axis, end_points = self._get_linear_selector_init_args(padding, **kwargs)

if selection is None:
Expand All @@ -56,17 +62,17 @@ def add_linear_selector(self, selection: int = None, padding: float = 50, **kwar

return weakref.proxy(selector)

def add_linear_region_selector(self, padding: float = 100.0, **kwargs) -> LinearRegionSelector:
def add_linear_region_selector(self, padding: float = None, **kwargs) -> LinearRegionSelector:
"""
Add a :class:`.LinearRegionSelector`. Selectors are just ``Graphic`` objects, so you can manage,
remove, or delete them from a plot area just like any other ``Graphic``.

Parameters
----------
padding: float, default 100.0
padding: float, optional
Extends the linear selector along the y-axis to make it easier to interact with.

kwargs
kwargs, optional
passed to ``LinearRegionSelector``

Returns
Expand All @@ -85,13 +91,13 @@ def add_linear_region_selector(self, padding: float = 100.0, **kwargs) -> Linear
size=size,
origin=origin,
parent=weakref.proxy(self),
fill_color=(0, 0, 0.35, 0.2),
**kwargs
)

self._plot_area.add_graphic(selector, center=False)
# so that it is above this graphic
selector.position.set_z(self.position.z + 3)
selector.fill.material.color = (*selector.fill.material.color[:-1], 0.2)

# PlotArea manages this for garbage collection etc. just like all other Graphics
# so we should only work with a proxy on the user-end
Expand All @@ -107,6 +113,14 @@ def _get_linear_selector_init_args(self, padding: float, **kwargs):
else:
axis = "x"

if padding is None:
if axis == "x":
# based on number of rows
padding = int(data.shape[0] * 0.15)
elif axis == "y":
# based on number of columns
padding = int(data.shape[1] * 0.15)

if axis == "x":
offset = self.position.x
# x limits, number of columns
Expand Down
1 change: 1 addition & 0 deletions fastplotlib/graphics/selectors/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from ._linear import LinearSelector
from ._linear_region import LinearRegionSelector
from ._rectangle_region import RectangleRegionSelector
from ._sync import Synchronizer
38 changes: 33 additions & 5 deletions fastplotlib/graphics/selectors/_base_selector.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,11 @@ def _add_plot_area_hook(self, plot_area):
# double-click to enable arrow-key moveable mode
wo.add_event_handler(self._toggle_arrow_key_moveable, "double_click")

for fill in self._fill:
if fill.material.color_is_transparent:
pfunc_fill = partial(self._check_fill_pointer_event, fill)
self._plot_area.renderer.add_event_handler(pfunc_fill, "pointer_down")

# when the pointer moves
self._plot_area.renderer.add_event_handler(self._move, "pointer_move")

Expand All @@ -129,14 +134,37 @@ def _add_plot_area_hook(self, plot_area):
self._plot_area.renderer.add_event_handler(self._key_up, "key_up")
self._plot_area.add_animations(self._key_hold)

def _check_fill_pointer_event(self, event_source: WorldObject, ev):
world_pos = self._plot_area.map_screen_to_world((ev.x, ev.y))
# outside viewport, ignore
# this shouldn't be possible since the event handler is registered to the fill mesh world object
# but I like sanity checks anyways
if world_pos is None:
return

bbox = event_source.get_world_bounding_box()

xmin, ymin, zmin = bbox[0]
xmax, ymax, zmax = bbox[1]

if not (xmin <= world_pos.x <= xmax):
return

if not (ymin <= world_pos.y <= ymax):
return

self._move_start(event_source, ev)

def _move_start(self, event_source: WorldObject, ev):
"""
Called on "pointer_down" events

Parameters
----------
event_source: WorldObject
event source, for example selection fill area ``Mesh`` an edge ``Line`` or vertex ``Points``
event source, for example selection fill area ``Mesh`` an edge ``Line`` or vertex ``Points``.
This helps keep the event source within the MoveInfo so that during "pointer_move" events (which are
from the renderer) we know the original source of the "move action".

ev: Event
pygfx ``Event``
Expand Down Expand Up @@ -179,14 +207,14 @@ def _move(self, ev):
self.delta = world_pos.clone().sub(self._move_info.last_position)
self._pygfx_event = ev

self._move_graphic(self.delta, ev)
self._move_graphic(self.delta)

# update last position
self._move_info.last_position = world_pos

self._plot_area.controller.enabled = True

def _move_graphic(self, delta, ev):
def _move_graphic(self, delta):
raise NotImplementedError("Must be implemented in subclass")

def _move_end(self, ev):
Expand Down Expand Up @@ -220,7 +248,7 @@ def _move_to_pointer(self, ev):
else:
self._move_info = MoveInfo(last_position=current_position, source=self._edges[0])

self._move_graphic(self.delta, ev)
self._move_graphic(self.delta)
self._move_info = None

def _pointer_enter(self, ev):
Expand Down Expand Up @@ -258,7 +286,7 @@ def _key_hold(self):
self._move_info = MoveInfo(last_position=None, source=self._edges[0])

# move the graphic
self._move_graphic(delta=delta, ev=None)
self._move_graphic(delta=delta)

self._move_info = None

Expand Down
4 changes: 2 additions & 2 deletions fastplotlib/graphics/selectors/_linear.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ def __init__(

line_data = np.column_stack([xs, ys, zs])
else:
raise ValueError("`axis` must be one of 'v' or 'h'")
raise ValueError("`axis` must be one of 'x' or 'y'")

line_data = line_data.astype(np.float32)

Expand Down Expand Up @@ -344,7 +344,7 @@ def _get_selected_index(self, graphic):
index = self.selection() - offset
return int(index)

def _move_graphic(self, delta: Vector3, ev):
def _move_graphic(self, delta: Vector3):
"""
Moves the graphic

Expand Down
18 changes: 5 additions & 13 deletions fastplotlib/graphics/selectors/_linear_region.py
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,8 @@ def __init__(
pygfx.box_geometry(size, 1, 1),
pygfx.MeshBasicMaterial(color=pygfx.Color(fill_color))
)
else:
raise ValueError("`axis` must be one of 'x' or 'y'")

# the fill of the selection
self.fill = mesh
Expand Down Expand Up @@ -314,7 +316,7 @@ def __init__(
def bounds(self) -> LinearBoundsFeature:
"""
The current bounds of the selection in world space. These bounds will NOT necessarily correspond to the
indices of the data that are under the selection. Use ``get_selected_indices()` which maps from
indices of the data that are under the selection. Use ``get_selected_indices()`` which maps from
world space to data indices.
"""
return self._bounds
Expand Down Expand Up @@ -433,8 +435,7 @@ def get_selected_indices(self, graphic: Graphic = None) -> Union[np.ndarray, Lis
ixs = np.arange(*self.bounds(), dtype=int)
return ixs

def _move_graphic(self, delta, ev):
# edge-0 bound current world position
def _move_graphic(self, delta):
if self.bounds.axis == "x":
# new left bound position
bound_pos_0 = Vector3(self.bounds()[0]).add(delta)
Expand All @@ -448,17 +449,8 @@ def _move_graphic(self, delta, ev):
# new top bound position
bound_pos_1 = Vector3(0, self.bounds()[1]).add(delta)

# workaround because transparent objects are not pickable in pygfx
if ev is not None:
if 2 in ev.buttons:
force_fill_move = True
else:
force_fill_move = False
else:
force_fill_move = False

# move entire selector if source was fill
if self._move_info.source == self.fill or force_fill_move:
if self._move_info.source == self.fill:
bound0 = getattr(bound_pos_0, self.bounds.axis)
bound1 = getattr(bound_pos_1, self.bounds.axis)
# set the new bounds
Expand Down
Loading