Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 6728939

Browse filesBrowse files
committed
can sync selectors, made add_linear_selector()
1 parent 03125dd commit 6728939
Copy full SHA for 6728939

File tree

Expand file treeCollapse file tree

5 files changed

+107
-40
lines changed
Filter options
Expand file treeCollapse file tree

5 files changed

+107
-40
lines changed

‎fastplotlib/graphics/_base.py

Copy file name to clipboardExpand all lines: fastplotlib/graphics/_base.py
+10Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,16 @@ def __repr__(self):
113113
else:
114114
return rval
115115

116+
def __eq__(self, other):
117+
# This is necessary because we use Graphics as weakref proxies
118+
if not isinstance(other, Graphic):
119+
raise TypeError("`==` operator is only valid between two Graphics")
120+
121+
if self.loc == other.loc:
122+
return True
123+
124+
return False
125+
116126
def __del__(self):
117127
del WORLD_OBJECTS[self.loc]
118128

‎fastplotlib/graphics/line.py

Copy file name to clipboardExpand all lines: fastplotlib/graphics/line.py
+28-2Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
from ._base import Graphic, Interaction, PreviouslyModifiedData
88
from .features import PointsDataFeature, ColorFeature, CmapFeature, ThicknessFeature
9-
from .selectors import LinearRegionSelector
9+
from .selectors import LinearRegionSelector, LinearSelector
1010
from ..utils import make_colors
1111

1212

@@ -99,6 +99,32 @@ def __init__(
9999
if z_position is not None:
100100
self.world_object.position.z = z_position
101101

102+
def add_linear_selector(self, selection: int = None, padding: float = 50, **kwargs):
103+
bounds_init, limits, size, origin, axis = self._get_linear_selector_init_args(padding, **kwargs)
104+
105+
if selection is None:
106+
selection = limits[0]
107+
108+
if selection < limits[0] or selection > limits[1]:
109+
raise ValueError(f"the passed selection: {selection} is beyond the limits: {limits}")
110+
111+
if axis == "x":
112+
end_points = (self.data()[:, 1].min() - padding, self.data()[:, 1].max() + padding)
113+
else:
114+
end_points = (self.data()[:, 0].min() - padding, self.data()[:, 0].max() + padding)
115+
116+
selector = LinearSelector(
117+
selection=selection,
118+
limits=limits,
119+
end_points=end_points,
120+
parent=self
121+
)
122+
123+
self._plot_area.add_graphic(selector, center=False)
124+
selector.position.z = self.position.z + 1
125+
126+
return weakref.proxy(selector)
127+
102128
def add_linear_region_selector(self, padding: float = 100.0, **kwargs) -> LinearRegionSelector:
103129
"""
104130
Add a :class:`.LinearRegionSelector`. Selectors are just ``Graphic`` objects, so you can manage,
@@ -178,7 +204,7 @@ def _get_linear_selector_init_args(self, padding: float, **kwargs):
178204
# initial bounds are 20% of the limits range
179205
bounds_init = (limits[0], int(np.ptp(limits) * 0.2) + offset)
180206

181-
return bounds_init, limits, size, origin
207+
return bounds_init, limits, size, origin, axis
182208

183209
def _add_plot_area_hook(self, plot_area):
184210
self._plot_area = plot_area
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1+
from ._linear import LinearSelector
12
from ._linear_region import LinearRegionSelector
2-

‎fastplotlib/graphics/selectors/_linear.py

Copy file name to clipboardExpand all lines: fastplotlib/graphics/selectors/_linear.py
+60-30Lines changed: 60 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -16,24 +16,25 @@
1616
from ..features._base import FeatureEvent
1717

1818

19-
class SliderValueFeature(GraphicFeature):
19+
class LinearSelectionFeature(GraphicFeature):
2020
# A bit much to have a class for this but this allows it to integrate with the fastplotlib callback system
2121
"""
22-
Manages the slider value and callbacks
22+
Manages the slider selection and callbacks
2323
2424
**pick info**
2525
2626
================== ================================================================
27-
key value
27+
key selection
2828
================== ================================================================
29-
"new_data" the new slider position in world coordinates
29+
"graphic" the selection graphic
3030
"selected_index" the graphic data index that corresponds to the slider position
31-
"world_object" parent world object
31+
"new_data" the new slider position in world coordinates
32+
"delta" the delta vector of the graphic in NDC
3233
================== ================================================================
3334
3435
"""
3536
def __init__(self, parent, axis: str, value: float):
36-
super(SliderValueFeature, self).__init__(parent, data=value)
37+
super(LinearSelectionFeature, self).__init__(parent, data=value)
3738

3839
self.axis = axis
3940

@@ -55,13 +56,19 @@ def _feature_changed(self, key: Union[int, slice, Tuple[slice]], new_data: Any):
5556
else:
5657
g_ix = None
5758

59+
# get pygfx event and reset it
60+
pygfx_ev = self._parent._pygfx_event
61+
self._parent._pygfx_event = None
62+
5863
pick_info = {
5964
"index": None,
6065
"collection-index": self._collection_index,
6166
"world_object": self._parent.world_object,
6267
"new_data": new_data,
6368
"selected_index": g_ix,
64-
"graphic": self._parent
69+
"graphic": self._parent,
70+
"delta": self._parent.delta,
71+
"pygfx_event": pygfx_ev
6572
}
6673

6774
event_data = FeatureEvent(type="slider", pick_info=pick_info)
@@ -70,9 +77,11 @@ def _feature_changed(self, key: Union[int, slice, Tuple[slice]], new_data: Any):
7077

7178

7279
class LinearSelector(Graphic):
80+
feature_events = ("selection")
81+
7382
def __init__(
7483
self,
75-
value: int,
84+
selection: int,
7685
limits: Tuple[int, int],
7786
axis: str = "x",
7887
parent: Graphic = None,
@@ -87,12 +96,12 @@ def __init__(
8796
8897
Parameters
8998
----------
99+
selection: int
100+
initial x or y selected value for the slider
101+
90102
axis: str, default "x"
91103
"x" | "y", the axis which the slider can move along
92104
93-
origin: int
94-
the initial position of the slider, x or y value depending on "axis" argument
95-
96105
end_points: (int, int)
97106
set length of slider by bounding it between two x-pos or two y-pos
98107
@@ -103,23 +112,23 @@ def __init__(
103112
thickness of the slider
104113
105114
color: Any, default "w"
106-
value to set the color of the slider
115+
selection to set the color of the slider
107116
108117
name: str, optional
109118
name of line slider
110119
111120
Features
112121
--------
113122
114-
value: :class:`SliderValueFeature`
115-
``value()`` returns the current slider position in world coordinates
116-
use ``value.add_event_handler()`` to add callback functions that are
117-
called when the LinearSelector value changes. See feaure class for event pick_info table
123+
selection: :class:`LinearSelectionFeature`
124+
``selection()`` returns the current slider position in world coordinates
125+
use ``selection.add_event_handler()`` to add callback functions that are
126+
called when the LinearSelector selection changes. See feaure class for event pick_info table
118127
119128
"""
120129

121130
self.limits = tuple(map(round, limits))
122-
value = round(value)
131+
selection = round(selection)
123132

124133
if axis == "x":
125134
xs = np.zeros(2)
@@ -171,27 +180,31 @@ def __init__(
171180
self._set_world_object(world_object)
172181

173182
# set x or y position
174-
pos = getattr(self.position, axis)
175-
pos = value
183+
if axis == "x":
184+
self.position.x = selection
185+
else:
186+
self.position.y = selection
176187

177-
self.value = SliderValueFeature(self, axis=axis, value=value)
188+
self.selection = LinearSelectionFeature(self, axis=axis, value=selection)
178189

179190
self.ipywidget_slider = ipywidget_slider
180191

181192
if self.ipywidget_slider is not None:
182193
self._setup_ipywidget_slider(ipywidget_slider)
183194

184195
self._move_info: dict = None
196+
self.delta = None
197+
self._pygfx_event = None
185198

186199
self.parent = parent
187200

188201
self._block_ipywidget_call = False
189202

190203
def _setup_ipywidget_slider(self, widget):
191204
# setup ipywidget slider with callbacks to this LinearSelector
192-
widget.value = int(self.value())
205+
widget.value = int(self.selection())
193206
widget.observe(self._ipywidget_callback, "value")
194-
self.value.add_event_handler(self._update_ipywidget)
207+
self.selection.add_event_handler(self._update_ipywidget)
195208
self._plot_area.renderer.add_event_handler(self._set_slider_layout, "resize")
196209

197210
def _update_ipywidget(self, ev):
@@ -205,7 +218,7 @@ def _ipywidget_callback(self, change):
205218
if self._block_ipywidget_call:
206219
return
207220

208-
self.value = change["new"]
221+
self.selection = change["new"]
209222

210223
def _set_slider_layout(self, *args):
211224
w, h = self._plot_area.renderer.logical_size
@@ -240,7 +253,7 @@ def make_ipywidget_slider(self, kind: str = "IntSlider", **kwargs):
240253
slider = cls(
241254
min=self.limits[0],
242255
max=self.limits[1],
243-
value=int(self.value()),
256+
value=int(self.selection()),
244257
step=1,
245258
**kwargs
246259
)
@@ -274,7 +287,7 @@ def get_selected_index(self, graphic: Graphic = None) -> int:
274287
to_search = graphic.data()[:, 1]
275288
offset = getattr(graphic.position, self.axis)
276289

277-
find_value = self.value() - offset
290+
find_value = self.selection() - offset
278291

279292
# get closest data index to the world space position of the slider
280293
idx = np.searchsorted(to_search, find_value, side="left")
@@ -327,9 +340,9 @@ def _move_to_pointer(self, ev):
327340
return
328341

329342
if self.axis == "x":
330-
self.value = world_pos.x
343+
self.selection = world_pos.x
331344
else:
332-
self.value = world_pos.y
345+
self.selection = world_pos.y
333346

334347
def _move_start(self, ev):
335348
self._move_info = {"last_pos": (ev.x, ev.y)}
@@ -346,13 +359,30 @@ def _move(self, ev):
346359
# pointer move events are in viewport or canvas space
347360
delta = Vector3(ev.x - last[0], ev.y - last[1])
348361

362+
self._pygfx_event = ev
363+
364+
self._move_graphic(delta)
365+
349366
self._move_info = {"last_pos": (ev.x, ev.y)}
367+
self._plot_area.controller.enabled = True
368+
369+
def _move_graphic(self, delta: Vector3):
370+
"""
371+
Moves the graphic, updates SelectionFeature
372+
373+
Parameters
374+
----------
375+
delta_ndc: Vector3
376+
the delta by which to move this Graphic, in screen coordinates
377+
378+
"""
379+
self.delta = delta.clone()
350380

351381
viewport_size = self._plot_area.viewport.logical_size
352382

353383
# convert delta to NDC coordinates using viewport size
354384
# also since these are just deltas we don't have to calculate positions relative to the viewport
355-
delta_ndc = delta.multiply(
385+
delta_ndc = delta.clone().multiply(
356386
Vector3(
357387
2 / viewport_size[0],
358388
-2 / viewport_size[1],
@@ -373,8 +403,8 @@ def _move(self, ev):
373403
if new_value < self.limits[0] or new_value > self.limits[1]:
374404
return
375405

376-
self.value = new_value
377-
self._plot_area.controller.enabled = True
406+
self.selection = new_value
407+
self.delta = None
378408

379409
def _move_end(self, ev):
380410
self._move_info = None

‎fastplotlib/layouts/_base.py

Copy file name to clipboardExpand all lines: fastplotlib/layouts/_base.py
+8-7Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from wgpu.gui.auto import WgpuCanvas
1010

1111
from ..graphics._base import Graphic, GraphicCollection
12-
from ..graphics.line_slider import LineSlider
12+
from ..graphics.selectors import LinearSelector
1313

1414

1515
# dict to store Graphic instances
@@ -82,7 +82,7 @@ def __init__(
8282
self._graphics: List[str] = list()
8383

8484
# hacky workaround for now to exclude from bbox calculations
85-
self._sliders: List[LineSlider] = list()
85+
self._selectors = list()
8686

8787
self.name = name
8888

@@ -212,9 +212,9 @@ def add_graphic(self, graphic: Graphic, center: bool = True):
212212
if graphic.name is not None: # skip for those that have no name
213213
self._check_graphic_name_exists(graphic.name)
214214

215-
# TODO: need to refactor LineSlider entirely
216-
if isinstance(graphic, LineSlider):
217-
self._sliders.append(graphic) # don't manage garbage collection of LineSliders for now
215+
# TODO: need to refactor LinearSelector entirely
216+
if isinstance(graphic, LinearSelector):
217+
self._selectors.append(graphic) # don't manage garbage collection of LineSliders for now
218218
else:
219219
# store in GRAPHICS dict
220220
loc = graphic.loc
@@ -293,7 +293,8 @@ def auto_scale(self, maintain_aspect: bool = False, zoom: float = 0.8):
293293
in the scene will fill the entire canvas.
294294
"""
295295
# hacky workaround for now until I figure out how to put it in its own scene
296-
for slider in self._sliders:
296+
# TODO: remove all selectors from a scene to calculate scene bbox
297+
for slider in self._selectors:
297298
self.scene.remove(slider.world_object)
298299

299300
self.center_scene()
@@ -306,7 +307,7 @@ def auto_scale(self, maintain_aspect: bool = False, zoom: float = 0.8):
306307
else:
307308
width, height, depth = (1, 1, 1)
308309

309-
for slider in self._sliders:
310+
for slider in self._selectors:
310311
self.scene.add(slider.world_object)
311312

312313
self.camera.width = width

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.