From 0608ed50bc06e2e2bb6e2a6ea4f80ffaa366a7a7 Mon Sep 17 00:00:00 2001 From: kushalkolar Date: Mon, 22 May 2023 02:31:56 -0400 Subject: [PATCH 1/5] WIP, mapping to points and selectors not updated --- fastplotlib/graphics/_base.py | 36 ++++++++++++++++--- fastplotlib/graphics/image.py | 4 +-- fastplotlib/graphics/line.py | 14 ++++---- fastplotlib/graphics/line_collection.py | 12 ++++--- fastplotlib/graphics/scatter.py | 2 +- .../graphics/selectors/_base_selector.py | 16 +++++---- fastplotlib/graphics/selectors/_linear.py | 15 ++++---- .../graphics/selectors/_linear_region.py | 5 ++- .../graphics/selectors/_rectangle_region.py | 1 - fastplotlib/layouts/_base.py | 6 ++-- fastplotlib/layouts/_subplot.py | 6 ++-- 11 files changed, 74 insertions(+), 43 deletions(-) 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..75f86a3b1 100644 --- a/fastplotlib/graphics/selectors/_base_selector.py +++ b/fastplotlib/graphics/selectors/_base_selector.py @@ -2,9 +2,13 @@ from dataclasses import dataclass from functools import partial -from pygfx.linalg import Vector3 from pygfx import WorldObject, Line, Mesh, Points +class dummy: + pass + +Vector3 = dummy + @dataclass class MoveInfo: @@ -22,10 +26,10 @@ class MoveInfo: # 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": (1, 0, 0), + "ArrowLeft": (-1, 0, 0), + "ArrowUp": (0, 1, 0), + "ArrowDown": (0, -1, 0), } @@ -225,7 +229,7 @@ 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 = self.position # middle mouse button clicks if ev.button != 3: diff --git a/fastplotlib/graphics/selectors/_linear.py b/fastplotlib/graphics/selectors/_linear.py index cb3b7ec27..89528a86a 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) @@ -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,7 +343,7 @@ def _get_selected_index(self, graphic): index = self.selection() - offset return int(index) - def _move_graphic(self, delta: Vector3): + def _move_graphic(self, delta): """ Moves the graphic diff --git a/fastplotlib/graphics/selectors/_linear_region.py b/fastplotlib/graphics/selectors/_linear_region.py index 142f40677..90069e99c 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.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.position_z = -1 self.world_object.add(edge) # set the initial bounds of the selector 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/layouts/_base.py b/fastplotlib/layouts/_base.py index 1571f51e9..99a1a12d7 100644 --- a/fastplotlib/layouts/_base.py +++ b/fastplotlib/layouts/_base.py @@ -5,7 +5,6 @@ from pygfx import Scene, OrthographicCamera, PerspectiveCamera, PanZoomController, OrbitController, \ Viewport, WgpuRenderer -from pygfx.linalg import Vector3 from wgpu.gui.auto import WgpuCanvas from ..graphics._base import Graphic, GraphicCollection @@ -158,7 +157,7 @@ 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: Tuple[float, float]): """ Map screen position to world position @@ -181,12 +180,13 @@ 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) 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): From e484f4187963d0d00242b994eb49f81a030155c5 Mon Sep 17 00:00:00 2001 From: kushalkolar Date: Tue, 23 May 2023 02:25:10 -0400 Subject: [PATCH 2/5] everything updated for linalg refactor except rectangle selector --- .../graphics/selectors/_base_selector.py | 49 +++++++++---------- fastplotlib/graphics/selectors/_linear.py | 10 ++-- .../graphics/selectors/_linear_region.py | 36 +++++++------- fastplotlib/graphics/text.py | 4 +- fastplotlib/layouts/_base.py | 17 ++++--- 5 files changed, 58 insertions(+), 58 deletions(-) diff --git a/fastplotlib/graphics/selectors/_base_selector.py b/fastplotlib/graphics/selectors/_base_selector.py index 75f86a3b1..64934b6fa 100644 --- a/fastplotlib/graphics/selectors/_base_selector.py +++ b/fastplotlib/graphics/selectors/_base_selector.py @@ -2,12 +2,9 @@ from dataclasses import dataclass from functools import partial -from pygfx import WorldObject, Line, Mesh, Points - -class dummy: - pass +import numpy as np -Vector3 = dummy +from pygfx import WorldObject, Line, Mesh, Points @dataclass @@ -18,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": (1, 0, 0), - "ArrowLeft": (-1, 0, 0), - "ArrowUp": (0, 1, 0), - "ArrowDown": (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]), } @@ -69,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 @@ -139,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 @@ -151,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) @@ -174,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, @@ -200,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) @@ -218,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): @@ -229,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.position + 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]) @@ -279,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 89528a86a..55b64b62f 100644 --- a/fastplotlib/graphics/selectors/_linear.py +++ b/fastplotlib/graphics/selectors/_linear.py @@ -188,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() @@ -343,18 +343,18 @@ def _get_selected_index(self, graphic): index = self.selection() - offset return int(index) - def _move_graphic(self, delta): + 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 90069e99c..30f223fad 100644 --- a/fastplotlib/graphics/selectors/_linear_region.py +++ b/fastplotlib/graphics/selectors/_linear_region.py @@ -233,7 +233,7 @@ def __init__( # the fill of the selection self.fill = mesh - self.fill.position = (*origin, -2) + self.fill.world.position = (*origin, -2) self.world_object.add(self.fill) @@ -295,7 +295,7 @@ def __init__( # add the edge lines for edge in self.edges: - edge.position_z = -1 + edge.world.z = -1 self.world_object.add(edge) # set the initial bounds of the selector @@ -399,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 @@ -434,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 @@ -463,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/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 99a1a12d7..f3781a4e7 100644 --- a/fastplotlib/layouts/_base.py +++ b/fastplotlib/layouts/_base.py @@ -3,8 +3,10 @@ import numpy as np +import pygfx from pygfx import Scene, OrthographicCamera, PerspectiveCamera, PanZoomController, OrbitController, \ Viewport, WgpuRenderer +from pylinalg import vec_transform, vec_unproject from wgpu.gui.auto import WgpuCanvas from ..graphics._base import Graphic, GraphicCollection @@ -157,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]): + 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 @@ -186,11 +190,12 @@ def map_screen_to_world(self, pos: Tuple[float, float]): 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() From 4b0da16c55812dfb4c5285084c43d6589d2f6670 Mon Sep 17 00:00:00 2001 From: kushalkolar Date: Tue, 23 May 2023 14:42:45 -0400 Subject: [PATCH 3/5] update toolbar w.r.t. linalg refactor --- fastplotlib/layouts/_gridplot.py | 2 +- fastplotlib/plot.py | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) 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/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 From 759d37ce8252bb1f90889648d8dad7a9c7ce03ec Mon Sep 17 00:00:00 2001 From: kushalkolar Date: Tue, 23 May 2023 15:00:40 -0400 Subject: [PATCH 4/5] update examples w.r.t. linalg refactor --- examples/linear_region_selector.ipynb | 16 +----- examples/linear_selector.ipynb | 12 ++++ examples/lineplot.ipynb | 67 ++-------------------- examples/scatter.ipynb | 81 ++++++--------------------- 4 files changed, 37 insertions(+), 139 deletions(-) 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, From 9a9dcf4231cbfcef4e1b0ff821a5fbad65d39f77 Mon Sep 17 00:00:00 2001 From: kushalkolar Date: Tue, 23 May 2023 15:17:36 -0400 Subject: [PATCH 5/5] update readme --- README.md | 3 +++ 1 file changed, 3 insertions(+) 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