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

Skip to content

catchup with pygfx linalg refactor #203

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 6 commits into from
May 23, 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
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ Questions, ideas? Post an issue or [chat on gitter](https://gitter.im/fastplotli

**See the examples directory. Start out with `simple.ipynb`.**

**IMPORTANT NOTE: If you install `fastplotlib` and `pygfx` from `pypi` (i.e. pip install pygfx), you will need to use the examples from this commit until `pygfx` publishes a new release to `pypi`: https://github.com/kushalkolar/fastplotlib/tree/f872155eb687b18e3cc9b3b720eb9e241a9f974c/examples .**
The current examples will work if you installed `fastplotlib` and `pygfx` directly from github.

### Neuroscience usecase demonstrating some of fastplotlib's capabilities

https://user-images.githubusercontent.com/9403332/210304485-e554b648-50b4-4243-b292-a9ed30514a2d.mp4
Expand Down
16 changes: 2 additions & 14 deletions examples/linear_region_selector.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@
"sine_graphic_y = gp[0, 1].add_line(np.column_stack([sine_y, xs]))\n",
"\n",
"# offset the position of the graphic to demonstrate `get_selected_data()` later\n",
"sine_graphic_y.position.set_x(50)\n",
"sine_graphic_y.position.set_y(50)\n",
"sine_graphic_y.position_x = 50\n",
"sine_graphic_y.position_y = 50\n",
"\n",
"# add linear selectors\n",
"ls_x = sine_graphic_x.add_linear_region_selector() # default axis is \"x\"\n",
Expand Down Expand Up @@ -256,18 +256,6 @@
"plot.show()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3fa61ffd-43d5-42d0-b3e1-5541f58185cd",
"metadata": {
"tags": []
},
"outputs": [],
"source": [
"plot[0, 0].auto_scale()"
]
},
{
"cell_type": "code",
"execution_count": null,
Expand Down
12 changes: 12 additions & 0 deletions examples/linear_selector.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,18 @@
"VBox([plot.show(), ipywidget_slider])"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a632c8ee-2d4c-44fc-9391-7b2880223fdb",
"metadata": {
"tags": []
},
"outputs": [],
"source": [
"selector.step = 0.1"
]
},
{
"cell_type": "markdown",
"id": "2c49cdc2-0555-410c-ae2e-da36c3bf3bf0",
Expand Down
67 changes: 5 additions & 62 deletions examples/lineplot.ipynb

Large diffs are not rendered by default.

81 changes: 18 additions & 63 deletions examples/scatter.ipynb

Large diffs are not rendered by default.

36 changes: 31 additions & 5 deletions fastplotlib/graphics/_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@
from .features._base import cleanup_slice

from pygfx import WorldObject, Group
from pygfx.linalg import Vector3

from .features import GraphicFeature, PresentFeature, GraphicFeatureIndexable

from abc import ABC, abstractmethod
Expand Down Expand Up @@ -83,10 +81,38 @@ def _set_world_object(self, wo: WorldObject):
WORLD_OBJECTS[hex(id(self))] = wo

@property
def position(self) -> Vector3:
def position(self) -> np.ndarray:
"""The position of the graphic. You can access or change
using position.x, position.y, etc."""
return self.world_object.position
return self.world_object.world.position

@property
def position_x(self) -> float:
return self.world_object.world.x

@property
def position_y(self) -> float:
return self.world_object.world.y

@property
def position_z(self) -> float:
return self.world_object.world.z

@position.setter
def position(self, val):
self.world_object.world.position = val

@position_x.setter
def position_x(self, val):
self.world_object.world.x = val

@position_y.setter
def position_y(self, val):
self.world_object.world.y = val

@position_z.setter
def position_z(self, val):
self.world_object.world.z = val

@property
def visible(self) -> bool:
Expand All @@ -99,7 +125,7 @@ def visible(self, v: bool):
self.world_object.visible = v

@property
def children(self) -> WorldObject:
def children(self) -> List[WorldObject]:
"""Return the children of the WorldObject."""
return self.world_object.children

Expand Down
4 changes: 2 additions & 2 deletions fastplotlib/graphics/image.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ def add_linear_selector(self, selection: int = None, padding: float = None, **kw
)

self._plot_area.add_graphic(selector, center=False)
selector.position.z = self.position.z + 1
selector.position_z = self.position_z + 1

return weakref.proxy(selector)

Expand Down Expand Up @@ -97,7 +97,7 @@ def add_linear_region_selector(self, padding: float = None, **kwargs) -> LinearR

self._plot_area.add_graphic(selector, center=False)
# so that it is above this graphic
selector.position.set_z(self.position.z + 3)
selector.position_z = self.position_z + 3

# 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 Down
14 changes: 7 additions & 7 deletions fastplotlib/graphics/line.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ def __init__(
self._set_world_object(world_object)

if z_position is not None:
self.world_object.position.z = z_position
self.position_z = z_position

def add_linear_selector(self, selection: int = None, padding: float = 50, **kwargs) -> LinearSelector:
"""
Expand Down Expand Up @@ -137,7 +137,7 @@ def add_linear_selector(self, selection: int = None, padding: float = 50, **kwar
)

self._plot_area.add_graphic(selector, center=False)
selector.position.z = self.position.z + 1
selector.position_z = self.position_z + 1

return weakref.proxy(selector)

Expand Down Expand Up @@ -175,7 +175,7 @@ def add_linear_region_selector(self, padding: float = 100.0, **kwargs) -> Linear

self._plot_area.add_graphic(selector, center=False)
# so that it is below this graphic
selector.position.set_z(self.position.z - 1)
selector.position_z = self.position_z - 1

# 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 @@ -192,7 +192,7 @@ def _get_linear_selector_init_args(self, padding: float, **kwargs):
axis = "x"

if axis == "x":
offset = self.position.x
offset = self.position_x
# x limits
limits = (data[0, 0] + offset, data[-1, 0] + offset)

Expand All @@ -203,13 +203,13 @@ def _get_linear_selector_init_args(self, padding: float, **kwargs):
position_y = (data[:, 1].min() + data[:, 1].max()) / 2

# need y offset too for this
origin = (limits[0] - offset, position_y + self.position.y)
origin = (limits[0] - offset, position_y + self.position_y)

# endpoints of the data range
# used by linear selector but not linear region
end_points = (self.data()[:, 1].min() - padding, self.data()[:, 1].max() + padding)
else:
offset = self.position.y
offset = self.position_y
# y limits
limits = (data[0, 1] + offset, data[-1, 1] + offset)

Expand All @@ -220,7 +220,7 @@ def _get_linear_selector_init_args(self, padding: float, **kwargs):
position_x = (data[:, 0].min() + data[:, 0].max()) / 2

# need x offset too for this
origin = (position_x + self.position.x, limits[0] - offset)
origin = (position_x + self.position_x, limits[0] - offset)

end_points = (self.data()[:, 0].min() - padding, self.data()[:, 0].max() + padding)

Expand Down
12 changes: 8 additions & 4 deletions fastplotlib/graphics/line_collection.py
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,7 @@ def add_linear_selector(self, selection: int = None, padding: float = 50, **kwar
)

self._plot_area.add_graphic(selector, center=False)
selector.position.z = self.position.z + 1
selector.position_z = self.position_z + 1

return weakref.proxy(selector)

Expand Down Expand Up @@ -302,7 +302,7 @@ def add_linear_region_selector(self, padding: float = 100.0, **kwargs) -> Linear
)

self._plot_area.add_graphic(selector, center=False)
selector.position.set_z(self.position.z - 1)
selector.position_z = self.position_z - 1

return weakref.proxy(selector)

Expand Down Expand Up @@ -346,7 +346,7 @@ def _get_linear_selector_init_args(self, padding, **kwargs):

# a better way to get the max y value?
# graphics y-position + data y-max + padding
end_points[1] = self.graphics[-1].position.y + self.graphics[-1].data()[:, 1].max() + padding
end_points[1] = self.graphics[-1].position_y + self.graphics[-1].data()[:, 1].max() + padding

else:
# just the biggest one if not stacked
Expand Down Expand Up @@ -521,7 +521,11 @@ def __init__(

axis_zero = 0
for i, line in enumerate(self.graphics):
getattr(line.position, f"set_{separation_axis}")(axis_zero)
if separation_axis == "x":
line.position_x = axis_zero
elif separation_axis == "y":
line.position_y = axis_zero

axis_zero = axis_zero + line.data()[:, axes[separation_axis]].max() + separation

self.separation = separation
2 changes: 1 addition & 1 deletion fastplotlib/graphics/scatter.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,4 +79,4 @@ def __init__(

self._set_world_object(world_object)

self.world_object.position.z = z_position
self.position_z = z_position
45 changes: 22 additions & 23 deletions fastplotlib/graphics/selectors/_base_selector.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
from dataclasses import dataclass
from functools import partial

from pygfx.linalg import Vector3
import numpy as np

from pygfx import WorldObject, Line, Mesh, Points


Expand All @@ -14,18 +15,18 @@ class MoveInfo:

# last position for an edge, fill, or vertex in world coordinates
# can be None, such as key events
last_position: Vector3 | None
last_position: Union[np.ndarray, None]

# WorldObject or "key" event
source: WorldObject | str
source: Union[WorldObject, str]


# key bindings used to move the selector
key_bind_direction = {
"ArrowRight": Vector3(1, 0, 0),
"ArrowLeft": Vector3(-1, 0, 0),
"ArrowUp": Vector3(0, 1, 0),
"ArrowDown": Vector3(0, -1, 0),
"ArrowRight": np.array([1, 0, 0]),
"ArrowLeft": np.array([-1, 0, 0]),
"ArrowUp": np.array([0, 1, 0]),
"ArrowDown": np.array([0, -1, 0]),
}


Expand Down Expand Up @@ -65,7 +66,7 @@ def __init__(
self.axis = axis

# current delta in world coordinates
self.delta: Vector3 = None
self.delta: np.ndarray = None

self.arrow_keys_modifier = arrow_keys_modifier
# if not False, moves the slider on every render cycle
Expand Down Expand Up @@ -135,7 +136,7 @@ def _add_plot_area_hook(self, plot_area):
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))
world_pos = self._plot_area.map_screen_to_world(ev)
# 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
Expand All @@ -147,10 +148,10 @@ def _check_fill_pointer_event(self, event_source: WorldObject, ev):
xmin, ymin, zmin = bbox[0]
xmax, ymax, zmax = bbox[1]

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

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

self._move_start(event_source, ev)
Expand All @@ -170,7 +171,7 @@ def _move_start(self, event_source: WorldObject, ev):
pygfx ``Event``

"""
last_position = self._plot_area.map_screen_to_world((ev.x, ev.y))
last_position = self._plot_area.map_screen_to_world(ev)

self._move_info = MoveInfo(
last_position=last_position,
Expand All @@ -196,15 +197,14 @@ def _move(self, ev):
self._plot_area.controller.enabled = False

# get pointer current world position
pointer_pos_screen = (ev.x, ev.y)
world_pos = self._plot_area.map_screen_to_world(pointer_pos_screen)
world_pos = self._plot_area.map_screen_to_world(ev)

# outside this viewport
if world_pos is None:
return

# compute the delta
self.delta = world_pos.clone().sub(self._move_info.last_position)
self.delta = world_pos - self._move_info.last_position
self._pygfx_event = ev

self._move_graphic(self.delta)
Expand All @@ -214,7 +214,7 @@ def _move(self, ev):

self._plot_area.controller.enabled = True

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

def _move_end(self, ev):
Expand All @@ -225,26 +225,25 @@ def _move_to_pointer(self, ev):
"""
Calculates delta just using current world object position and calls self._move_graphic().
"""
current_position = self.world_object.position.clone()
current_position: np.ndarray = self.position

# middle mouse button clicks
if ev.button != 3:
return

click_pos = (ev.x, ev.y)
world_pos = self._plot_area.map_screen_to_world(click_pos)
world_pos = self._plot_area.map_screen_to_world(ev)

# outside this viewport
if world_pos is None:
return

self.delta = world_pos.clone().sub(current_position)
self.delta = world_pos - current_position
self._pygfx_event = ev

# use fill by default as the source
# use fill by default as the source, such as in region selectors
if len(self._fill) > 0:
self._move_info = MoveInfo(last_position=current_position, source=self._fill[0])
# else use an edge
# else use an edge, such as for linear selector
else:
self._move_info = MoveInfo(last_position=current_position, source=self._edges[0])

Expand Down Expand Up @@ -275,7 +274,7 @@ def _toggle_arrow_key_moveable(self, ev):
def _key_hold(self):
if self._key_move_value and self.arrow_key_events_enabled:
# direction vector * step
delta = key_bind_direction[self._key_move_value].clone().multiply_scalar(self.step)
delta = key_bind_direction[self._key_move_value] * self.step

# set event source
# use fill by default as the source
Expand Down
Loading