1
- from typing import *
2
1
import math
3
2
from numbers import Real
3
+ from typing import Sequence
4
4
5
5
import numpy as np
6
6
import pygfx
7
7
8
- from ...utils .gui import IS_JUPYTER
9
8
from .._base import Graphic
10
9
from .._collection_base import GraphicCollection
11
10
from .._features ._selection_features import LinearSelectionFeature
12
11
from ._base_selector import BaseSelector
13
12
14
13
15
- if IS_JUPYTER :
16
- # If using the jupyter backend, user has jupyter_rfb, and thus also ipywidgets
17
- import ipywidgets
18
-
19
-
20
14
class LinearSelector (BaseSelector ):
21
15
@property
22
16
def parent (self ) -> Graphic :
@@ -39,11 +33,11 @@ def selection(self, value: int):
39
33
self ._selection .set_value (self , value )
40
34
41
35
@property
42
- def limits (self ) -> Tuple [float , float ]:
36
+ def limits (self ) -> tuple [float , float ]:
43
37
return self ._limits
44
38
45
39
@limits .setter
46
- def limits (self , values : Tuple [float , float ]):
40
+ def limits (self , values : tuple [float , float ]):
47
41
# check that `values` is an iterable of two real numbers
48
42
# using `Real` here allows it to work with builtin `int` and `float` types, and numpy scaler types
49
43
if len (values ) != 2 or not all (map (lambda v : isinstance (v , Real ), values )):
@@ -62,46 +56,50 @@ def __init__(
62
56
center : float ,
63
57
axis : str = "x" ,
64
58
parent : Graphic = None ,
65
- color : str | tuple = "w" ,
59
+ color : str | Sequence [ float ] | np . ndarray = "w" ,
66
60
thickness : float = 2.5 ,
67
61
arrow_keys_modifier : str = "Shift" ,
68
62
name : str = None ,
69
63
):
70
64
"""
71
- Create a horizontal or vertical line slider that is synced to an ipywidget IntSlider
65
+ Create a horizontal or vertical line that can be used to select a value along an axis.
72
66
73
67
Parameters
74
68
----------
75
69
selection: int
76
- initial x or y selected position for the slider , in world space
70
+ initial x or y selected position for the selector , in data space
77
71
78
72
limits: (int, int)
79
- (min, max) limits along the x or y axis for the selector, in world space
73
+ (min, max) limits along the x or y- axis for the selector, in data space
80
74
81
- axis: str, default "x"
82
- "x" | "y", the axis which the slider can move along
75
+ size: float
76
+ size of the selector, usually the range of the data
83
77
84
78
center: float
85
- center offset of the selector on the orthogonal axis, by default the data mean
79
+ center offset of the selector on the orthogonal axis, usually the data mean
80
+
81
+ axis: str, default "x"
82
+ "x" | "y", the axis along which the selector can move
86
83
87
84
parent: Graphic
88
- parent graphic for this LineSelector
85
+ parent graphic for this LinearSelector
89
86
90
87
arrow_keys_modifier: str
91
88
modifier key that must be pressed to initiate movement using arrow keys, must be one of:
92
- "Control", "Shift", "Alt" or ``None``. Double click on the selector first to enable the
89
+ "Control", "Shift", "Alt" or ``None``. Double- click the selector first to enable the
93
90
arrow key movements, or set the attribute ``arrow_key_events_enabled = True``
94
91
95
92
thickness: float, default 2.5
96
- thickness of the slider
93
+ thickness of the selector
97
94
98
- color: Any , default "w"
99
- selection to set the color of the slider
95
+ color: str | tuple | np.ndarray , default "w"
96
+ color of the selector
100
97
101
98
name: str, optional
102
- name of line slider
99
+ name of linear selector
103
100
104
101
"""
102
+
105
103
if len (limits ) != 2 :
106
104
raise ValueError ("limits must be a tuple of 2 integers, i.e. (int, int)" )
107
105
@@ -155,10 +153,6 @@ def __init__(
155
153
156
154
self ._move_info : dict = None
157
155
158
- self ._block_ipywidget_call = False
159
-
160
- self ._handled_widgets = list ()
161
-
162
156
if axis == "x" :
163
157
offset = (parent .offset [0 ], center + parent .offset [1 ], 0 )
164
158
elif axis == "y" :
@@ -187,149 +181,22 @@ def __init__(
187
181
else :
188
182
self ._selection .set_value (self , selection )
189
183
190
- # update any ipywidgets
191
- self .add_event_handler (self ._update_ipywidgets , "selection" )
192
-
193
- def _setup_ipywidget_slider (self , widget ):
194
- # setup an ipywidget slider with bidirectional callbacks to this LinearSelector
195
- value = self .selection
196
-
197
- if isinstance (widget , ipywidgets .IntSlider ):
198
- value = int (value )
199
-
200
- widget .value = value
201
-
202
- # user changes widget -> linear selection changes
203
- widget .observe (self ._ipywidget_callback , "value" )
204
-
205
- self ._handled_widgets .append (widget )
206
-
207
- def _update_ipywidgets (self , ev ):
208
- # update the ipywidget sliders when LinearSelector value changes
209
- self ._block_ipywidget_call = True # prevent infinite recursion
210
-
211
- value = ev .info ["value" ]
212
- # update all the handled slider widgets
213
- for widget in self ._handled_widgets :
214
- if isinstance (widget , ipywidgets .IntSlider ):
215
- widget .value = int (value )
216
- else :
217
- widget .value = value
218
-
219
- self ._block_ipywidget_call = False
220
-
221
- def _ipywidget_callback (self , change ):
222
- # update the LinearSelector when the ipywidget value changes
223
- if self ._block_ipywidget_call or self ._moving :
224
- return
225
-
226
- self .selection = change ["new" ]
227
-
228
- def _fpl_add_plot_area_hook (self , plot_area ):
229
- super ()._fpl_add_plot_area_hook (plot_area = plot_area )
230
-
231
- # resize the slider widgets when the canvas is resized
232
- self ._plot_area .renderer .add_event_handler (self ._set_slider_layout , "resize" )
233
-
234
- def _set_slider_layout (self , * args ):
235
- w , h = self ._plot_area .renderer .logical_size
236
-
237
- for widget in self ._handled_widgets :
238
- widget .layout = ipywidgets .Layout (width = f"{ w } px" )
239
-
240
- def make_ipywidget_slider (self , kind : str = "IntSlider" , ** kwargs ):
184
+ def get_selected_index (self , graphic : Graphic = None ) -> int | list [int ]:
241
185
"""
242
- Makes and returns an ipywidget slider that is associated to this LinearSelector
186
+ Data index the selector is currently at w.r.t. the Graphic data.
243
187
244
- Parameters
245
- ----------
246
- kind: str
247
- "IntSlider", "FloatSlider" or "FloatLogSlider"
248
-
249
- kwargs
250
- passed to the ipywidget slider constructor
251
-
252
- Returns
253
- -------
254
- ipywidgets.Intslider or ipywidgets.FloatSlider
255
-
256
- """
257
-
258
- if not IS_JUPYTER :
259
- raise ImportError (
260
- "Must installed `ipywidgets` to use `make_ipywidget_slider()`"
261
- )
262
-
263
- if kind not in ["IntSlider" , "FloatSlider" , "FloatLogSlider" ]:
264
- raise TypeError (
265
- f"`kind` must be one of: 'IntSlider', 'FloatSlider' or 'FloatLogSlider'\n "
266
- f"You have passed: '{ kind } '"
267
- )
268
-
269
- cls = getattr (ipywidgets , kind )
270
-
271
- value = self .selection
272
- if "Int" in kind :
273
- value = int (self .selection )
274
-
275
- slider = cls (
276
- min = self .limits [0 ],
277
- max = self .limits [1 ],
278
- value = value ,
279
- ** kwargs ,
280
- )
281
- self .add_ipywidget_handler (slider )
282
-
283
- return slider
284
-
285
- def add_ipywidget_handler (self , widget , step : Union [int , float ] = None ):
286
- """
287
- Bidirectionally connect events with a ipywidget slider
288
-
289
- Parameters
290
- ----------
291
- widget: ipywidgets.IntSlider, ipywidgets.FloatSlider, or ipywidgets.FloatLogSlider
292
- ipywidget slider to connect to
293
-
294
- step: int or float, default ``None``
295
- step size, if ``None`` 100 steps are created
296
-
297
- """
298
-
299
- if not isinstance (
300
- widget ,
301
- (ipywidgets .IntSlider , ipywidgets .FloatSlider , ipywidgets .FloatLogSlider ),
302
- ):
303
- raise TypeError (
304
- f"`widget` must be one of: ipywidgets.IntSlider, ipywidgets.FloatSlider, or ipywidgets.FloatLogSlider\n "
305
- f"You have passed a: <{ type (widget )} "
306
- )
307
-
308
- if step is None :
309
- step = (self .limits [1 ] - self .limits [0 ]) / 100
310
-
311
- if isinstance (widget , ipywidgets .IntSlider ):
312
- step = int (step )
313
-
314
- widget .step = step
315
-
316
- self ._setup_ipywidget_slider (widget )
317
-
318
- def get_selected_index (self , graphic : Graphic = None ) -> Union [int , List [int ]]:
319
- """
320
- Data index the slider is currently at w.r.t. the Graphic data. With LineGraphic data, the geometry x or y
321
- position is not always the data position, for example if plotting data using np.linspace. Use this to get
322
- the data index of the slider.
188
+ With LineGraphic data, the geometry x or y position is not always the data position, for example if plotting
189
+ data using np.linspace. Use this to get the data index of the selector.
323
190
324
191
Parameters
325
192
----------
326
193
graphic: Graphic, optional
327
- Graphic to get the selected data index from. Default is the parent graphic associated to the slider .
194
+ Graphic to get the selected data index from. Default is the parent graphic associated to the selector .
328
195
329
196
Returns
330
197
-------
331
198
int or List[int]
332
- data index the slider is currently at, list of ``int`` if a Collection
199
+ data index the selector is currently at, list of ``int`` if a Collection
333
200
"""
334
201
335
202
source = self ._get_source (graphic )
@@ -354,10 +221,10 @@ def _get_selected_index(self, graphic):
354
221
"Line" in graphic .__class__ .__name__
355
222
or "Scatter" in graphic .__class__ .__name__
356
223
):
357
- # we want to find the index of the data closest to the slider position
224
+ # we want to find the index of the data closest to the selector position
358
225
find_value = self .selection
359
226
360
- # get closest data index to the world space position of the slider
227
+ # get closest data index to the world space position of the selector
361
228
idx = np .searchsorted (data , find_value , side = "left" )
362
229
363
230
if idx > 0 and (
@@ -398,9 +265,3 @@ def _move_graphic(self, delta: np.ndarray):
398
265
self .selection = self .selection + delta [0 ]
399
266
else :
400
267
self .selection = self .selection + delta [1 ]
401
-
402
- def _fpl_prepare_del (self ):
403
- for widget in self ._handled_widgets :
404
- widget .unobserve (self ._ipywidget_callback , "value" )
405
-
406
- super ()._fpl_prepare_del ()
0 commit comments