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

Skip to content
Draft
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
45 changes: 45 additions & 0 deletions fastplotlib/graphics/features/_selection_features.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,51 @@
from ...utils.triangulation import triangulate


class PointSelectionFeature(GraphicFeature):
event_info_spec = [
{
"dict key": "value",
"type": "np.ndarray",
"description": "new (x, y, z) value of selection",
},
]

def __init__(self, value: np.ndarray):
"""
Parameters
----------
value : np.ndarray
position of the selector in world space, NOT data space
"""

super().__init__()

self._value = value

@property
def value(self) -> np.ndarray:
"""
selection, data (x, y, z)
"""
return self._value

@block_reentrance
def set_value(self, selector, value: np.ndarray):
if value.shape != (1, 3):
raise ValueError("Shape of new value must be of a single point: (1, 3)")

for vertex in selector._vertices:
vertex.geometry.positions.data[:] = value
vertex.geometry.positions.update_range()

self._value = value

event = GraphicFeatureEvent("selection", {"value": value})
event.get_selected_index = selector.get_selected_index

self._call_event_handlers(event)


class LinearSelectionFeature(GraphicFeature):
event_info_spec = [
{
Expand Down
30 changes: 30 additions & 0 deletions fastplotlib/graphics/line.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@

from ._positions_base import PositionsGraphic
from .selectors import (
BallSelector,
LinearRegionSelector,
LinearSelector,
RectangleSelector,
PolygonSelector,
__all__,
)
from .features import (
Thickness,
Expand Down Expand Up @@ -137,6 +139,34 @@ def thickness(self) -> float:
def thickness(self, value: float):
self._thickness.set_value(self, value)

def add_ball_selector(self, selection: float = None, **kwargs) -> BallSelector:
"""
Adds a :class: `.BallSelector`.

Parameters
----------
selection: float, optional
selected point on the linear selector, by default the first datapoint on the line.

kwargs
passed to :class:`.BallSelector`

Returns
-------
BallSelector
"""
if selection is None:
selection = self.data.value[0].reshape(1, 3)

selector = BallSelector(selection=selection, parent=self, **kwargs)

self._plot_area.add_graphic(selector, center=False)

# place selector above this graphic
selector.offset = selector.offset + (0.0, 0.0, self.offset[-1] + 1)

return selector

def add_linear_selector(
self, selection: float = None, axis: str = "x", **kwargs
) -> LinearSelector:
Expand Down
8 changes: 7 additions & 1 deletion fastplotlib/graphics/selectors/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@
from ._linear_region import LinearRegionSelector
from ._polygon import PolygonSelector
from ._rectangle import RectangleSelector
from ._ball import BallSelector


__all__ = ["LinearSelector", "LinearRegionSelector", "RectangleSelector"]
__all__ = [
"LinearSelector",
"LinearRegionSelector",
"RectangleSelector",
"BallSelector",
]
176 changes: 176 additions & 0 deletions fastplotlib/graphics/selectors/_ball.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
from typing import Sequence
import math

import numpy as np
import pygfx

from .._base import Graphic
from ..features._selection_features import PointSelectionFeature
from ._base_selector import BaseSelector, MoveInfo
from ..features import UniformSize


class BallSelector(BaseSelector):
_features = {"selection": PointSelectionFeature}

@property
def parent(self) -> Graphic:
return self._parent

@property
def selection(self) -> np.ndarray:
"""Value of selector's current position (x, y, z)"""
return self._selection.value

@selection.setter
def selection(self, value: np.ndarray):
"""
Set the (x, y, z) position of the selector. If bound to a parent graphic, will set
selection to the nearest point of the parent.

Parameters
----------
value : np.ndarray
New (x, y, z) position of the selector
"""
if value.shape != (1, 3):
raise ValueError("Selection must be a single (x, y, z) point")
# if selector is bound to a parent graphic, find the nearest data point
if self.parent is not None:
closest_ix = self._get_nearest_index(self.parent, value)
value = self.parent.data[closest_ix].reshape(1, 3)
self._selection.set_value(self, value)

@property
def color(self) -> pygfx.Color:
"""Returns the color of the ball selector."""
return self._color

@color.setter
def color(self, color: str | Sequence[float]):
"""
Set the color of the ball selector.

Parameters
----------
color : str | Sequence[float]
String or sequence of floats that gets converted into a ``pygfx.Color`` object.
"""
color = pygfx.Color(color)
self.world_object.material.color = color
self._original_colors[self._vertices[0]] = color
self._color = color

@property
def size(self) -> float:
"""Returns the size of the ball selector."""
if isinstance(self._size, UniformSize):
return self._size.value

@size.setter
def size(self, value: float):
"""
Set the size of the ball selector.

Parameters
----------
value : float
Size of the ball selector
"""
if isinstance(self._size, UniformSize):
self._size.set_value(self, value)

def __init__(
self,
selection: np.ndarray,
parent: Graphic = None,
color: str | Sequence[float] | np.ndarray = "w",
size: float = 10,
arrow_keys_modifier: str = "Shift",
name: str = None,
):
"""
Create a ball marker that can be used to select a value along a line

Parameters
----------
selection: np.ndarray
(x, y, z) position of the selector, in data space
parent: Graphic
parent graphic for the BallSelector
color: str | tuple | np.ndarray, default "w"
color of the selector
size: float
size of the selector
arrow_keys_modifier: str
modifier key that must be pressed to initiate movement using arrow keys, must be one of:
"Control", "Shift", "Alt" or ``None``. Double-click the selector first to enable the
arrow key movements, or set the attribute ``arrow_key_events_enabled = True``
name: str, optional
name of linear selector

"""
self._color = pygfx.Color(color)

geo_kwargs = {"positions": selection}

material_kwargs = {"pick_write": True}
material_kwargs["color_mode"] = "uniform"
material_kwargs["color"] = self._color

material_kwargs["size_mode"] = "uniform"
self._size = UniformSize(size)
material_kwargs["size"] = self.size

world_object = pygfx.Points(
pygfx.Geometry(**geo_kwargs),
material=pygfx.PointsMaterial(**material_kwargs),
)

# init base selector
BaseSelector.__init__(
self,
vertices=(world_object,),
hover_responsive=(world_object,),
arrow_keys_modifier=arrow_keys_modifier,
parent=parent,
name=name,
)

self._set_world_object(world_object)

self._selection = PointSelectionFeature(value=selection)

if self._parent is not None:
self.selection = selection
else:
self._selection.set_value(self, selection)

def _get_nearest_index(self, graphic, find_value):
data = graphic.data.value[:]

# get closest data index to the world space position of the selector
distances = np.sum((data - find_value) ** 2, axis=1)

# Index of closest point
idx = np.argmin(distances)

return idx

def _move_graphic(self, move_info: MoveInfo):
"""
Moves the graphic

Parameters
----------
delta: np.ndarray
delta in world space

"""
# If this the first move in this drag, store initial selection
if move_info.start_selection is None:
move_info.start_selection = self.selection

delta = move_info.delta

self.selection = move_info.start_selection + delta