11from typing import *
22import weakref
33from warnings import warn
4+ from abc import ABC , abstractmethod
5+ from dataclasses import dataclass
46
57import numpy as np
68
7- from .features ._base import cleanup_slice
8-
9- from pygfx import WorldObject , Group
10- from .features import GraphicFeature , PresentFeature , GraphicFeatureIndexable
11-
12- from abc import ABC , abstractmethod
13- from dataclasses import dataclass
9+ from pygfx import WorldObject
1410
11+ from ._features import GraphicFeature , PresentFeature , GraphicFeatureIndexable
1512
1613# dict that holds all world objects for a given python kernel/session
1714# Graphic objects only use proxies to WorldObjects
3734class BaseGraphic :
3835 def __init_subclass__ (cls , ** kwargs ):
3936 """set the type of the graphic in lower case like "image", "line_collection", etc."""
40- cls .type = cls . __name__ \
41- . lower ()\
42- .replace ("graphic" , "" )\
43- .replace ("collection" , "_collection" )\
37+ cls .type = (
38+ cls . __name__ . lower ()
39+ .replace ("graphic" , "" )
40+ .replace ("collection" , "_collection" )
4441 .replace ("stack" , "_stack" )
42+ )
4543
4644 super ().__init_subclass__ (** kwargs )
4745
4846
4947class Graphic (BaseGraphic ):
5048 def __init__ (
51- self ,
52- name : str = None ,
53- metadata : Any = None ,
54- collection_index : int = None ,
49+ self ,
50+ name : str = None ,
51+ metadata : Any = None ,
52+ collection_index : int = None ,
5553 ):
5654 """
5755
@@ -163,6 +161,7 @@ def __del__(self):
163161
164162class Interaction (ABC ):
165163 """Mixin class that makes graphics interactive"""
164+
166165 @abstractmethod
167166 def _set_feature (self , feature : str , new_data : Any , indices : Any ):
168167 pass
@@ -172,13 +171,13 @@ def _reset_feature(self, feature: str):
172171 pass
173172
174173 def link (
175- self ,
176- event_type : str ,
177- target : Any ,
178- feature : str ,
179- new_data : Any ,
180- callback : callable = None ,
181- bidirectional : bool = False
174+ self ,
175+ event_type : str ,
176+ target : Any ,
177+ feature : str ,
178+ new_data : Any ,
179+ callback : callable = None ,
180+ bidirectional : bool = False ,
182181 ):
183182 """
184183 Link this graphic to another graphic upon an ``event_type`` to change the ``feature``
@@ -192,24 +191,30 @@ def link(
192191 or appropriate feature event (ex. colors, data, etc.) associated with the graphic (can use
193192 ``graphic_instance.feature_events`` to get a tuple of the valid feature events for the
194193 graphic)
194+
195195 target: Any
196196 graphic to be linked to
197+
197198 feature: str
198199 feature (ex. colors, data, etc.) of the target graphic that will change following
199200 the event
201+
200202 new_data: Any
201203 appropriate data that will be changed in the feature of the target graphic after
202204 the event occurs
205+
203206 callback: callable, optional
204207 user-specified callable that will handle event,
205208 the callable must take the following four arguments
206209 | ''source'' - this graphic instance
207210 | ''target'' - the graphic to be changed following the event
208211 | ''event'' - the ''pygfx event'' or ''feature event'' that occurs
209212 | ''new_data'' - the appropriate data of the ''target'' that will be changed
213+
210214 bidirectional: bool, default False
211215 if True, the target graphic is also linked back to this graphic instance using the
212216 same arguments
217+
213218 For example:
214219 .. code-block::python
215220
@@ -231,21 +236,32 @@ def link(
231236 feature_instance .add_event_handler (self ._event_handler )
232237
233238 else :
234- raise ValueError (f"Invalid event, valid events are: { PYGFX_EVENTS + self .feature_events } " )
239+ raise ValueError (
240+ f"Invalid event, valid events are: { PYGFX_EVENTS + self .feature_events } "
241+ )
235242
236243 # make sure target feature is valid
237244 if feature is not None :
238245 if feature not in target .feature_events :
239- raise ValueError (f"Invalid feature for target, valid features are: { target .feature_events } " )
246+ raise ValueError (
247+ f"Invalid feature for target, valid features are: { target .feature_events } "
248+ )
240249
241250 if event_type not in self .registered_callbacks .keys ():
242251 self .registered_callbacks [event_type ] = list ()
243252
244- callback_data = CallbackData (target = target , feature = feature , new_data = new_data , callback_function = callback )
253+ callback_data = CallbackData (
254+ target = target ,
255+ feature = feature ,
256+ new_data = new_data ,
257+ callback_function = callback ,
258+ )
245259
246260 for existing_callback_data in self .registered_callbacks [event_type ]:
247261 if existing_callback_data == callback_data :
248- warn ("linkage already exists for given event, target, and data, skipping" )
262+ warn (
263+ "linkage already exists for given event, target, and data, skipping"
264+ )
249265 return
250266
251267 self .registered_callbacks [event_type ].append (callback_data )
@@ -254,15 +270,15 @@ def link(
254270 if event_type in PYGFX_EVENTS :
255271 warn ("cannot use bidirectional link for pygfx events" )
256272 return
257-
273+
258274 target .link (
259275 event_type = event_type ,
260276 target = self ,
261277 feature = feature ,
262278 new_data = new_data ,
263279 callback = callback ,
264280 bidirectional = False # else infinite recursion, otherwise target will call
265- # this instance .link(), and then it will happen again etc.
281+ # this instance .link(), and then it will happen again etc.
266282 )
267283
268284 def _event_handler (self , event ):
@@ -271,7 +287,12 @@ def _event_handler(self, event):
271287 for target_info in self .registered_callbacks [event .type ]:
272288 if target_info .callback_function is not None :
273289 # if callback_function is not None, then callback function should handle the entire event
274- target_info .callback_function (source = self , target = target_info .target , event = event , new_data = target_info .new_data )
290+ target_info .callback_function (
291+ source = self ,
292+ target = target_info .target ,
293+ event = event ,
294+ new_data = target_info .new_data ,
295+ )
275296
276297 elif isinstance (self , GraphicCollection ):
277298 # if target is a GraphicCollection, then indices will be stored in collection_index
@@ -288,16 +309,24 @@ def _event_handler(self, event):
288309 # the real world object in the pick_info and not the proxy
289310 if wo is event .pick_info ["world_object" ]:
290311 indices = i
291- target_info .target ._set_feature (feature = target_info .feature , new_data = target_info .new_data , indices = indices )
312+ target_info .target ._set_feature (
313+ feature = target_info .feature ,
314+ new_data = target_info .new_data ,
315+ indices = indices ,
316+ )
292317 else :
293318 # if target is a single graphic, then indices do not matter
294- target_info .target ._set_feature (feature = target_info .feature , new_data = target_info .new_data ,
295- indices = None )
319+ target_info .target ._set_feature (
320+ feature = target_info .feature ,
321+ new_data = target_info .new_data ,
322+ indices = None ,
323+ )
296324
297325
298326@dataclass
299327class CallbackData :
300328 """Class for keeping track of the info necessary for interactivity after event occurs."""
329+
301330 target : Any
302331 feature : str
303332 new_data : Any
@@ -329,6 +358,7 @@ def __eq__(self, other):
329358@dataclass
330359class PreviouslyModifiedData :
331360 """Class for keeping track of previously modified data at indices"""
361+
332362 data : Any
333363 indices : Any
334364
@@ -350,7 +380,9 @@ def __init__(self, name: str = None):
350380 def graphics (self ) -> np .ndarray [Graphic ]:
351381 """The Graphics within this collection. Always returns a proxy to the Graphics."""
352382 if self ._graphics_changed :
353- proxies = [weakref .proxy (COLLECTION_GRAPHICS [loc ]) for loc in self ._graphics ]
383+ proxies = [
384+ weakref .proxy (COLLECTION_GRAPHICS [loc ]) for loc in self ._graphics
385+ ]
354386 self ._graphics_array = np .array (proxies )
355387 self ._graphics_array .flags ["WRITEABLE" ] = False
356388 self ._graphics_changed = False
@@ -395,15 +427,14 @@ def __getitem__(self, key):
395427 return CollectionIndexer (
396428 parent = self ,
397429 selection = self .graphics [key ],
398- # selection_indices=key
399430 )
400-
431+
401432 def __del__ (self ):
402433 self .world_object .clear ()
403434
404435 for loc in self ._graphics :
405436 del COLLECTION_GRAPHICS [loc ]
406-
437+
407438 super ().__del__ ()
408439
409440 def _reset_index (self ):
@@ -420,11 +451,11 @@ def __repr__(self):
420451
421452class CollectionIndexer :
422453 """Collection Indexer"""
454+
423455 def __init__ (
424- self ,
425- parent : GraphicCollection ,
426- selection : List [Graphic ],
427- # selection_indices: Union[list, range],
456+ self ,
457+ parent : GraphicCollection ,
458+ selection : List [Graphic ],
428459 ):
429460 """
430461
@@ -436,26 +467,22 @@ def __init__(
436467 selection: list of Graphics
437468 a list of the selected Graphics from the parent GraphicCollection based on the ``selection_indices``
438469
439- selection_indices: Union[list, range]
440- the corresponding indices from the parent GraphicCollection that were selected
441470 """
442471
443472 self ._parent = weakref .proxy (parent )
444473 self ._selection = selection
445- # self._selection_indices = selection_indices
446474
447475 # we use parent.graphics[0] instead of selection[0]
448476 # because the selection can be empty
449477 for attr_name in self ._parent .graphics [0 ].__dict__ .keys ():
450478 attr = getattr (self ._parent .graphics [0 ], attr_name )
451479 if isinstance (attr , GraphicFeature ):
452480 collection_feature = CollectionFeature (
453- parent ,
454- self . _selection ,
455- # selection_indices=self._selection_indices,
456- feature = attr_name
481+ self . _selection , feature = attr_name
482+ )
483+ collection_feature . __doc__ = (
484+ f"indexable < { attr_name } > feature for collection"
457485 )
458- collection_feature .__doc__ = f"indexable <{ attr_name } > feature for collection"
459486 setattr (self , attr_name , collection_feature )
460487
461488 @property
@@ -476,31 +503,26 @@ def __len__(self):
476503 return len (self ._selection )
477504
478505 def __repr__ (self ):
479- return f"{ self .__class__ .__name__ } @ { hex (id (self ))} \n " \
480- f"Selection of <{ len (self ._selection )} > { self ._selection [0 ].__class__ .__name__ } "
506+ return (
507+ f"{ self .__class__ .__name__ } @ { hex (id (self ))} \n "
508+ f"Selection of <{ len (self ._selection )} > { self ._selection [0 ].__class__ .__name__ } "
509+ )
481510
482511
483512class CollectionFeature :
484513 """Collection Feature"""
485- def __init__ (
486- self ,
487- parent : GraphicCollection ,
488- selection : List [Graphic ],
489- # selection_indices,
490- feature : str
491- ):
514+
515+ def __init__ (self , selection : List [Graphic ], feature : str ):
492516 """
493- parent: GraphicCollection
494- GraphicCollection feature instance that is being indexed
495517 selection: list of Graphics
496518 a list of the selected Graphics from the parent GraphicCollection based on the ``selection_indices``
497- selection_indices: Union[list, range]
498- the corresponding indices from the parent GraphicCollection that were selected
519+
499520 feature: str
500521 feature of Graphics in the GraphicCollection being indexed
522+
501523 """
524+
502525 self ._selection = selection
503- # self._selection_indices = selection_indices
504526 self ._feature = feature
505527
506528 self ._feature_instances : List [GraphicFeature ] = list ()
@@ -550,4 +572,3 @@ def block_events(self, b: bool):
550572
551573 def __repr__ (self ):
552574 return f"Collection feature for: <{ self ._feature } >"
553-
0 commit comments