diff --git a/README.md b/README.md
index 3999a15ea..a6e567a25 100644
--- a/README.md
+++ b/README.md
@@ -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
diff --git a/examples/linear_region_selector.ipynb b/examples/linear_region_selector.ipynb
index 8a3ae6cd0..8aeb37e27 100644
--- a/examples/linear_region_selector.ipynb
+++ b/examples/linear_region_selector.ipynb
@@ -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",
@@ -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,
diff --git a/examples/linear_selector.ipynb b/examples/linear_selector.ipynb
index a00225a5f..a4d6b97ea 100644
--- a/examples/linear_selector.ipynb
+++ b/examples/linear_selector.ipynb
@@ -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",
diff --git a/examples/lineplot.ipynb b/examples/lineplot.ipynb
index 36b921cf3..52ce66547 100644
--- a/examples/lineplot.ipynb
+++ b/examples/lineplot.ipynb
@@ -12,7 +12,7 @@
},
{
"cell_type": "code",
- "execution_count": 1,
+ "execution_count": null,
"id": "9c974494-712e-4981-bae2-a3ee176a6b20",
"metadata": {},
"outputs": [],
@@ -23,7 +23,7 @@
},
{
"cell_type": "code",
- "execution_count": 2,
+ "execution_count": null,
"id": "c3d8f967-f60f-4f0b-b6ba-21b1251b4856",
"metadata": {},
"outputs": [],
@@ -41,60 +41,10 @@
},
{
"cell_type": "code",
- "execution_count": 3,
+ "execution_count": null,
"id": "78cffe56-1147-4255-82c1-53cec6bc986a",
"metadata": {},
- "outputs": [
- {
- "data": {
- "application/vnd.jupyter.widget-view+json": {
- "model_id": "7993e0a4358f4678a7343b78b3b0b24c",
- "version_major": 2,
- "version_minor": 0
- },
- "text/plain": [
- "RFBOutputContext()"
- ]
- },
- "metadata": {},
- "output_type": "display_data"
- },
- {
- "name": "stderr",
- "output_type": "stream",
- "text": [
- "/home/kushalk/repos/fastplotlib/fastplotlib/layouts/_base.py:214: UserWarning: `center_scene()` not yet implemented for `PerspectiveCamera`\n",
- " warn(\"`center_scene()` not yet implemented for `PerspectiveCamera`\")\n"
- ]
- },
- {
- "data": {
- "text/html": [
- "

initial snapshot
"
- ],
- "text/plain": [
- ""
- ]
- },
- "metadata": {},
- "output_type": "display_data"
- },
- {
- "data": {
- "application/vnd.jupyter.widget-view+json": {
- "model_id": "898a109f489741a5b4624be77bd27db0",
- "version_major": 2,
- "version_minor": 0
- },
- "text/plain": [
- "JupyterWgpuCanvas()"
- ]
- },
- "execution_count": 3,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
+ "outputs": [],
"source": [
"# grid with 2 rows and 2 columns\n",
"shape = (2, 2)\n",
@@ -124,13 +74,6 @@
" subplot.set_axes_visibility(True)\n",
" subplot.set_grid_visibility(True)\n",
" \n",
- " # invert the camera for some subplots to get\n",
- " # different perspectives on the same data\n",
- " if i == 1:\n",
- " subplot.camera.scale.x = -1\n",
- " if i == 2:\n",
- " subplot.camera.scale.y = -1\n",
- " \n",
" marker = subplot.add_scatter(data=spiral[0], sizes=10, name=\"marker\")\n",
" \n",
"marker_index = 0\n",
@@ -178,7 +121,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
- "version": "3.10.5"
+ "version": "3.11.3"
}
},
"nbformat": 4,
diff --git a/examples/scatter.ipynb b/examples/scatter.ipynb
index e32b7a5b6..2253a3387 100644
--- a/examples/scatter.ipynb
+++ b/examples/scatter.ipynb
@@ -12,7 +12,7 @@
},
{
"cell_type": "code",
- "execution_count": 1,
+ "execution_count": null,
"id": "9b3041ad-d94e-4b2a-af4d-63bcd19bf6c2",
"metadata": {
"tags": []
@@ -25,7 +25,7 @@
},
{
"cell_type": "code",
- "execution_count": 2,
+ "execution_count": null,
"id": "51f1d76a-f815-460f-a884-097fe3ea81ac",
"metadata": {},
"outputs": [],
@@ -54,60 +54,10 @@
},
{
"cell_type": "code",
- "execution_count": 3,
+ "execution_count": null,
"id": "922990b6-24e9-4fa0-977b-6577f9752d84",
"metadata": {},
- "outputs": [
- {
- "data": {
- "application/vnd.jupyter.widget-view+json": {
- "model_id": "0d49371132174eb4a9501964b4584d67",
- "version_major": 2,
- "version_minor": 0
- },
- "text/plain": [
- "RFBOutputContext()"
- ]
- },
- "metadata": {},
- "output_type": "display_data"
- },
- {
- "name": "stderr",
- "output_type": "stream",
- "text": [
- "/home/kushalk/repos/fastplotlib/fastplotlib/layouts/_base.py:214: UserWarning: `center_scene()` not yet implemented for `PerspectiveCamera`\n",
- " warn(\"`center_scene()` not yet implemented for `PerspectiveCamera`\")\n"
- ]
- },
- {
- "data": {
- "text/html": [
- "
initial snapshot
"
- ],
- "text/plain": [
- ""
- ]
- },
- "metadata": {},
- "output_type": "display_data"
- },
- {
- "data": {
- "application/vnd.jupyter.widget-view+json": {
- "model_id": "a94246496c054599bc44a0a77ea7d58e",
- "version_major": 2,
- "version_minor": 0
- },
- "text/plain": [
- "JupyterWgpuCanvas()"
- ]
- },
- "execution_count": 3,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
+ "outputs": [],
"source": [
"# grid with 2 rows and 2 columns\n",
"shape = (2, 2)\n",
@@ -142,16 +92,13 @@
" subplot.set_axes_visibility(True)\n",
" subplot.set_grid_visibility(True)\n",
"\n",
- "# different perspectives on the synced views\n",
- "grid_plot[1, 0].camera.scale.x = -1\n",
- "grid_plot[1, 1].camera.scale.y = -1\n",
"\n",
"grid_plot.show()"
]
},
{
"cell_type": "code",
- "execution_count": 4,
+ "execution_count": null,
"id": "7b912961-f72e-46ef-889f-c03234831059",
"metadata": {},
"outputs": [],
@@ -161,7 +108,7 @@
},
{
"cell_type": "code",
- "execution_count": 5,
+ "execution_count": null,
"id": "c6085806-c001-4632-ab79-420b4692693a",
"metadata": {},
"outputs": [],
@@ -171,7 +118,7 @@
},
{
"cell_type": "code",
- "execution_count": 6,
+ "execution_count": null,
"id": "6f416825-df31-4e5d-b66b-07f23b48e7db",
"metadata": {},
"outputs": [],
@@ -181,7 +128,7 @@
},
{
"cell_type": "code",
- "execution_count": 7,
+ "execution_count": null,
"id": "c0fd611e-73e5-49e6-a25c-9d5b64afa5f4",
"metadata": {},
"outputs": [],
@@ -191,7 +138,7 @@
},
{
"cell_type": "code",
- "execution_count": 9,
+ "execution_count": null,
"id": "cd390542-3a44-4973-8172-89e5583433bc",
"metadata": {},
"outputs": [],
@@ -206,6 +153,14 @@
"metadata": {},
"outputs": [],
"source": []
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "dc1e8581-0f6b-49d1-af7a-98920bef2eb0",
+ "metadata": {},
+ "outputs": [],
+ "source": []
}
],
"metadata": {
@@ -224,7 +179,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
- "version": "3.10.5"
+ "version": "3.11.3"
}
},
"nbformat": 4,
diff --git a/fastplotlib/graphics/_base.py b/fastplotlib/graphics/_base.py
index 9eee05555..6ec76f625 100644
--- a/fastplotlib/graphics/_base.py
+++ b/fastplotlib/graphics/_base.py
@@ -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
@@ -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:
@@ -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
diff --git a/fastplotlib/graphics/image.py b/fastplotlib/graphics/image.py
index 9ba9a4143..8773569c0 100644
--- a/fastplotlib/graphics/image.py
+++ b/fastplotlib/graphics/image.py
@@ -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)
@@ -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
diff --git a/fastplotlib/graphics/line.py b/fastplotlib/graphics/line.py
index 9a1fb1cb6..dbfdfc40e 100644
--- a/fastplotlib/graphics/line.py
+++ b/fastplotlib/graphics/line.py
@@ -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:
"""
@@ -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)
@@ -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
@@ -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)
@@ -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)
@@ -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)
diff --git a/fastplotlib/graphics/line_collection.py b/fastplotlib/graphics/line_collection.py
index 3b5deac65..be223677d 100644
--- a/fastplotlib/graphics/line_collection.py
+++ b/fastplotlib/graphics/line_collection.py
@@ -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)
@@ -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)
@@ -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
@@ -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
diff --git a/fastplotlib/graphics/scatter.py b/fastplotlib/graphics/scatter.py
index b53985de0..cfad8e7ce 100644
--- a/fastplotlib/graphics/scatter.py
+++ b/fastplotlib/graphics/scatter.py
@@ -79,4 +79,4 @@ def __init__(
self._set_world_object(world_object)
- self.world_object.position.z = z_position
+ self.position_z = z_position
diff --git a/fastplotlib/graphics/selectors/_base_selector.py b/fastplotlib/graphics/selectors/_base_selector.py
index 165b322da..64934b6fa 100644
--- a/fastplotlib/graphics/selectors/_base_selector.py
+++ b/fastplotlib/graphics/selectors/_base_selector.py
@@ -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
@@ -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]),
}
@@ -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
@@ -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
@@ -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)
@@ -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,
@@ -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)
@@ -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):
@@ -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])
@@ -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
diff --git a/fastplotlib/graphics/selectors/_linear.py b/fastplotlib/graphics/selectors/_linear.py
index cb3b7ec27..55b64b62f 100644
--- a/fastplotlib/graphics/selectors/_linear.py
+++ b/fastplotlib/graphics/selectors/_linear.py
@@ -4,7 +4,6 @@
import numpy as np
import pygfx
-from pygfx.linalg import Vector3
try:
import ipywidgets
@@ -45,9 +44,9 @@ def _set(self, value: float):
return
if self.axis == "x":
- self._parent.position.x = value
+ self._parent.position_x = value
else:
- self._parent.position.y = value
+ self._parent.position_y = value
self._data = value
self._feature_changed(key=None, new_data=value)
@@ -189,7 +188,7 @@ def __init__(
material=material(thickness=thickness + 6, color=self.colors_outer)
)
- line_inner.position.z = self.line_outer.position.z + 1
+ line_inner.world.z = self.line_outer.world.z + 1
world_object = pygfx.Group()
@@ -200,9 +199,9 @@ def __init__(
# set x or y position
if axis == "x":
- self.position.x = selection
+ self.position_x = selection
else:
- self.position.y = selection
+ self.position_y = selection
self.selection = LinearSelectionFeature(self, axis=axis, value=selection, limits=limits)
@@ -322,10 +321,10 @@ def _get_selected_index(self, graphic):
# the array to search for the closest value along that axis
if self.axis == "x":
geo_positions = graphic.data()[:, 0]
- offset = getattr(graphic.position, self.axis)
+ offset = getattr(graphic, f"position_{self.axis}")
else:
geo_positions = graphic.data()[:, 1]
- offset = getattr(graphic.position, self.axis)
+ offset = getattr(graphic, f"position_{self.axis}")
if "Line" in graphic.__class__.__name__:
# we want to find the index of the geometry position that is closest to the slider's geometry position
@@ -344,18 +343,18 @@ def _get_selected_index(self, graphic):
index = self.selection() - offset
return int(index)
- def _move_graphic(self, delta: Vector3):
+ def _move_graphic(self, delta: np.ndarray):
"""
Moves the graphic
Parameters
----------
- delta: Vector3
+ delta: np.ndarray
delta in world space
"""
if self.axis == "x":
- self.selection = self.selection() + delta.x
+ self.selection = self.selection() + delta[0]
else:
- self.selection = self.selection() + delta.y
+ self.selection = self.selection() + delta[1]
diff --git a/fastplotlib/graphics/selectors/_linear_region.py b/fastplotlib/graphics/selectors/_linear_region.py
index 142f40677..30f223fad 100644
--- a/fastplotlib/graphics/selectors/_linear_region.py
+++ b/fastplotlib/graphics/selectors/_linear_region.py
@@ -2,7 +2,6 @@
import numpy as np
import pygfx
-from pygfx.linalg import Vector3
from .._base import Graphic, GraphicCollection
from ..features._base import GraphicFeature, FeatureEvent
@@ -234,7 +233,7 @@ def __init__(
# the fill of the selection
self.fill = mesh
- self.fill.position.set(*origin, -2)
+ self.fill.world.position = (*origin, -2)
self.world_object.add(self.fill)
@@ -296,7 +295,7 @@ def __init__(
# add the edge lines
for edge in self.edges:
- edge.position.set_z(-1)
+ edge.world.z = -1
self.world_object.add(edge)
# set the initial bounds of the selector
@@ -400,7 +399,7 @@ def get_selected_indices(self, graphic: Graphic = None) -> Union[np.ndarray, Lis
source = self._get_source(graphic)
# if the graphic position is not at (0, 0) then the bounds must be offset
- offset = getattr(source.position, self.bounds.axis)
+ offset = getattr(source, f"position_{self.bounds.axis}")
offset_bounds = tuple(v - offset for v in self.bounds())
# need them to be int to use as indices
@@ -435,26 +434,31 @@ 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):
+ def _move_graphic(self, delta: np.ndarray):
+ # add delta to current bounds to get new positions
if self.bounds.axis == "x":
+ # min and max of current bounds, i.e. the edges
+ xmin, xmax = self.bounds()
+
# new left bound position
- bound_pos_0 = Vector3(self.bounds()[0]).add(delta)
+ bound0_new = xmin + delta[0]
# new right bound position
- bound_pos_1 = Vector3(self.bounds()[1]).add(delta)
+ bound1_new = xmax + delta[0]
else:
+ # min and max of current bounds, i.e. the edges
+ ymin, ymax = self.bounds()
+
# new bottom bound position
- bound_pos_0 = Vector3(0, self.bounds()[0]).add(delta)
+ bound0_new = ymin + delta[1]
# new top bound position
- bound_pos_1 = Vector3(0, self.bounds()[1]).add(delta)
+ bound1_new = ymax + delta[1]
# move entire selector if source was fill
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
- self.bounds = (bound0, bound1)
+ self.bounds = (bound0_new, bound1_new)
return
# if selector is not resizable do nothing
@@ -464,15 +468,10 @@ def _move_graphic(self, delta):
# if resizable, move edges
if self._move_info.source == self.edges[0]:
# change only left or bottom bound
- bound0 = getattr(bound_pos_0, self.bounds.axis)
- bound1 = self.bounds()[1]
+ self.bounds = (bound0_new, self.bounds()[1])
elif self._move_info.source == self.edges[1]:
# change only right or top bound
- bound0 = self.bounds()[0]
- bound1 = getattr(bound_pos_1, self.bounds.axis)
+ self.bounds = (self.bounds()[0], bound1_new)
else:
return
-
- # set the new bounds
- self.bounds = (bound0, bound1)
diff --git a/fastplotlib/graphics/selectors/_rectangle_region.py b/fastplotlib/graphics/selectors/_rectangle_region.py
index 6332f9bcc..7065abe2d 100644
--- a/fastplotlib/graphics/selectors/_rectangle_region.py
+++ b/fastplotlib/graphics/selectors/_rectangle_region.py
@@ -2,7 +2,6 @@
import numpy as np
import pygfx
-from pygfx.linalg import Vector3
from .._base import Graphic, GraphicCollection
from ..features._base import GraphicFeature, FeatureEvent
diff --git a/fastplotlib/graphics/text.py b/fastplotlib/graphics/text.py
index 8225bb300..42bc3dba8 100644
--- a/fastplotlib/graphics/text.py
+++ b/fastplotlib/graphics/text.py
@@ -39,13 +39,13 @@ def __init__(
super(TextGraphic, self).__init__(name=name)
world_object = pygfx.Text(
- pygfx.TextGeometry(text=text, font_size=size, screen_space=False),
+ pygfx.TextGeometry(text=str(text), font_size=size, screen_space=False),
pygfx.TextMaterial(color=face_color, outline_color=outline_color, outline_thickness=outline_thickness)
)
self._set_world_object(world_object)
- self.world_object.position.set(*position)
+ self.world_object.position = position
self.name = None
diff --git a/fastplotlib/layouts/_base.py b/fastplotlib/layouts/_base.py
index 1571f51e9..f3781a4e7 100644
--- a/fastplotlib/layouts/_base.py
+++ b/fastplotlib/layouts/_base.py
@@ -3,9 +3,10 @@
import numpy as np
+import pygfx
from pygfx import Scene, OrthographicCamera, PerspectiveCamera, PanZoomController, OrbitController, \
Viewport, WgpuRenderer
-from pygfx.linalg import Vector3
+from pylinalg import vec_transform, vec_unproject
from wgpu.gui.auto import WgpuCanvas
from ..graphics._base import Graphic, GraphicCollection
@@ -158,16 +159,18 @@ def get_rect(self) -> Tuple[float, float, float, float]:
"""allows setting the region occupied by the viewport w.r.t. the parent"""
raise NotImplementedError("Must be implemented in subclass")
- def map_screen_to_world(self, pos: Tuple[float, float]) -> Vector3:
+ def map_screen_to_world(self, pos: Union[Tuple[float, float], pygfx.PointerEvent]) -> np.ndarray:
"""
Map screen position to world position
Parameters
----------
- pos: (float, float)
- (x, y) screen coordinates
+ pos: (float, float) | pygfx.PointerEvent
+ ``(x, y)`` screen coordinates, or ``pygfx.PointerEvent``
"""
+ if isinstance(pos, pygfx.PointerEvent):
+ pos = pos.x, pos.y
if not self.viewport.is_inside(*pos):
return None
@@ -181,16 +184,18 @@ def map_screen_to_world(self, pos: Tuple[float, float]) -> Vector3:
)
# convert screen position to NDC
- pos_ndc = Vector3(
+ pos_ndc = (
pos_rel[0] / vs[0] * 2 - 1,
-(pos_rel[1] / vs[1] * 2 - 1),
0
)
# get world position
- pos_world = self.camera.position.clone().project(self.camera).add(pos_ndc).unproject(self.camera)
+ pos_ndc += vec_transform(self.camera.world.position, self.camera.camera_matrix)
+ pos_world = vec_unproject(pos_ndc[:2], self.camera.camera_matrix)
- return pos_world
+ # default z is zero for now
+ return np.array([*pos_world[:2], 0])
def set_viewport_rect(self, *args):
self.viewport.rect = self.get_rect()
diff --git a/fastplotlib/layouts/_gridplot.py b/fastplotlib/layouts/_gridplot.py
index c121e42f2..734d004d4 100644
--- a/fastplotlib/layouts/_gridplot.py
+++ b/fastplotlib/layouts/_gridplot.py
@@ -385,7 +385,7 @@ def maintain_aspect(self, obj):
def flip_camera(self, obj):
current = self.current_subplot
- current.camera.scale.y = -1 * current.camera.scale.y
+ current.camera.world.scale_y *= -1
def update_current_subplot(self, ev):
for subplot in self.plot:
diff --git a/fastplotlib/layouts/_subplot.py b/fastplotlib/layouts/_subplot.py
index 1f3da61d9..0ce84ddcd 100644
--- a/fastplotlib/layouts/_subplot.py
+++ b/fastplotlib/layouts/_subplot.py
@@ -151,9 +151,9 @@ def center_title(self):
if self._title_graphic is None:
raise AttributeError("No title graphic is set")
- self._title_graphic.world_object.position.set(0, 0, 0)
+ self._title_graphic.world_object.position = (0, 0, 0)
self.docked_viewports["top"].center_graphic(self._title_graphic, zoom=1.5)
- self._title_graphic.world_object.position.y = -3.5
+ self._title_graphic.world_object.position_y = -3.5
def get_rect(self):
"""Returns the bounding box that defines the Subplot within the canvas."""
@@ -260,7 +260,7 @@ def remove_animation(self, func):
def add_graphic(self, graphic, center: bool = True):
"""Adds a Graphic to the subplot."""
- graphic.world_object.position.z = len(self._graphics)
+ graphic.position_z = len(self._graphics)
super(Subplot, self).add_graphic(graphic, center)
def set_axes_visibility(self, visible: bool):
diff --git a/fastplotlib/plot.py b/fastplotlib/plot.py
index 105dbfb96..6f8e1bc44 100644
--- a/fastplotlib/plot.py
+++ b/fastplotlib/plot.py
@@ -184,5 +184,4 @@ def maintain_aspect(self, obj):
self.plot.camera.maintain_aspect = self.maintain_aspect_button.value
def flip_camera(self, obj):
- self.plot.camera.scale.y = -1 * self.plot.camera.scale.y
-
\ No newline at end of file
+ self.plot.camera.world.scale_y *= -1