Skip to content

Commit 4a64391

Browse files
authored
Merge pull request #106 from kushalkolar/more-interaction
More interaction
2 parents 368a2ce + 1d3ca3e commit 4a64391

File tree

5 files changed

+98
-18
lines changed

5 files changed

+98
-18
lines changed

fastplotlib/graphics/_base.py

Lines changed: 71 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
from typing import *
2+
from warnings import warn
3+
4+
import numpy as np
25

36
from .features._base import cleanup_slice
47

@@ -92,10 +95,19 @@ def _set_feature(self, feature: str, new_data: Any, indices: Any):
9295
def _reset_feature(self, feature: str):
9396
pass
9497

95-
def link(self, event_type: str, target: Any, feature: str, new_data: Any, callback_function: callable = None):
98+
def link(
99+
self,
100+
event_type: str,
101+
target: Any,
102+
feature: str,
103+
new_data: Any,
104+
callback: callable = None,
105+
bidirectional: bool = False
106+
):
96107
if event_type in self.pygfx_events:
97108
self.world_object.add_event_handler(self.event_handler, event_type)
98109

110+
# make sure event is valid
99111
elif event_type in self.feature_events:
100112
if isinstance(self, GraphicCollection):
101113
feature_instance = getattr(self[:], event_type)
@@ -105,15 +117,35 @@ def link(self, event_type: str, target: Any, feature: str, new_data: Any, callba
105117
feature_instance.add_event_handler(self.event_handler)
106118

107119
else:
108-
raise ValueError("event not possible")
120+
raise ValueError(f"Invalid event, valid events are: {self.pygfx_events + self.feature_events}")
109121

110-
if event_type in self.registered_callbacks.keys():
111-
self.registered_callbacks[event_type].append(
112-
CallbackData(target=target, feature=feature, new_data=new_data, callback_function=callback_function))
113-
else:
122+
# make sure target feature is valid
123+
if feature is not None:
124+
if feature not in target.feature_events:
125+
raise ValueError(f"Invalid feature for target, valid features are: {target.feature_events}")
126+
127+
if event_type not in self.registered_callbacks.keys():
114128
self.registered_callbacks[event_type] = list()
115-
self.registered_callbacks[event_type].append(
116-
CallbackData(target=target, feature=feature, new_data=new_data, callback_function=callback_function))
129+
130+
callback_data = CallbackData(target=target, feature=feature, new_data=new_data, callback_function=callback)
131+
132+
for existing_callback_data in self.registered_callbacks[event_type]:
133+
if existing_callback_data == callback_data:
134+
warn("linkage already exists for given event, target, and data, skipping")
135+
return
136+
137+
self.registered_callbacks[event_type].append(callback_data)
138+
139+
if bidirectional:
140+
target.link(
141+
event_type=event_type,
142+
target=self,
143+
feature=feature,
144+
new_data=new_data,
145+
callback=callback,
146+
bidirectional=False # else infinite recursion, otherwise target will call
147+
# this instance .link(), and then it will happen again etc.
148+
)
117149

118150
def event_handler(self, event):
119151
if event.type in self.registered_callbacks.keys():
@@ -145,6 +177,28 @@ class CallbackData:
145177
new_data: Any
146178
callback_function: callable = None
147179

180+
def __eq__(self, other):
181+
if not isinstance(other, CallbackData):
182+
raise TypeError("Can only compare against other <CallbackData> types")
183+
184+
if other.target is not self.target:
185+
return False
186+
187+
if not other.feature == self.feature:
188+
return False
189+
190+
if not other.new_data == self.new_data:
191+
return False
192+
193+
if (self.callback_function is None) and (other.callback_function is None):
194+
return True
195+
196+
if other.callback_function is self.callback_function:
197+
return True
198+
199+
else:
200+
return False
201+
148202

149203
@dataclass
150204
class PreviouslyModifiedData:
@@ -156,10 +210,6 @@ class PreviouslyModifiedData:
156210
class GraphicCollection(Graphic):
157211
"""Graphic Collection base class"""
158212

159-
pygfx_events = [
160-
"click"
161-
]
162-
163213
def __init__(self, name: str = None):
164214
super(GraphicCollection, self).__init__(name)
165215
self._items: List[Graphic] = list()
@@ -207,14 +257,21 @@ def __getitem__(self, key):
207257
selection = self._items[key]
208258

209259
# fancy-ish indexing
210-
elif isinstance(key, (tuple, list)):
260+
elif isinstance(key, (tuple, list, np.ndarray)):
261+
if isinstance(key, np.ndarray):
262+
if not key.ndim == 1:
263+
raise TypeError(f"{self.__class__.__name__} indexing supports "
264+
f"1D numpy arrays, int, slice, tuple or list of integers, "
265+
f"your numpy arrays has <{key.ndim}> dimensions.")
211266
selection = list()
267+
212268
for ix in key:
213269
selection.append(self._items[ix])
214270

215271
selection_indices = key
216272
else:
217-
raise TypeError(f"Graphic Collection indexing supports int, slice, tuple or list of integers, "
273+
raise TypeError(f"{self.__class__.__name__} indexing supports "
274+
f"1D numpy arrays, int, slice, tuple or list of integers, "
218275
f"you have passed a <{type(key)}>")
219276

220277
return CollectionIndexer(

fastplotlib/graphics/features/_base.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -107,8 +107,13 @@ def _call_event_handlers(self, event_data: FeatureEvent):
107107

108108
for func in self._event_handlers:
109109
try:
110-
if len(getfullargspec(func).args) > 0:
111-
func(event_data)
110+
args = getfullargspec(func).args
111+
112+
if len(args) > 0:
113+
if args[0] == "self" and not len(args) > 1:
114+
func()
115+
else:
116+
func(event_data)
112117
else:
113118
func()
114119
except:

fastplotlib/graphics/image.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,9 @@
1010
class ImageGraphic(Graphic, Interaction):
1111
feature_events = [
1212
"data",
13-
"colors",
13+
"cmap",
1414
]
15+
1516
def __init__(
1617
self,
1718
data: Any,

fastplotlib/graphics/line.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ class LineGraphic(Graphic, Interaction):
1111
feature_events = [
1212
"data",
1313
"colors",
14+
"cmap",
15+
"thickness",
16+
"present"
1417
]
1518

1619
def __init__(

fastplotlib/graphics/line_collection.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ class LineCollection(GraphicCollection, Interaction):
1414
feature_events = [
1515
"data",
1616
"colors",
17+
"cmap",
18+
"thickness",
19+
"present"
1720
]
1821

1922
def __init__(
@@ -122,6 +125,13 @@ def _set_feature(self, feature: str, new_data: Any, indices: Any):
122125
if not hasattr(self, "_previous_data"):
123126
self._previous_data = dict()
124127
elif hasattr(self, "_previous_data"):
128+
if feature in self._previous_data.keys():
129+
# for now assume same index won't be changed with diff data
130+
# I can't think of a usecase where we'd have to check the data too
131+
# so unless there is bug we keep it like this
132+
if self._previous_data[feature].indices == indices:
133+
return # nothing to change, and this allows bidirectional linking without infinite recusion
134+
125135
self._reset_feature(feature)
126136

127137
coll_feature = getattr(self[indices], feature)
@@ -132,14 +142,18 @@ def _set_feature(self, feature: str, new_data: Any, indices: Any):
132142

133143
# later we can think about multi-index events
134144
previous = deepcopy(data[0])
135-
coll_feature._set(new_data)
136145

137146
if feature in self._previous_data.keys():
138147
self._previous_data[feature].data = previous
139148
self._previous_data[feature].indices = indices
140149
else:
141150
self._previous_data[feature] = PreviouslyModifiedData(data=previous, indices=indices)
142151

152+
# finally set the new data
153+
# this MUST occur after setting the previous data attribute to prevent recursion
154+
# since calling `feature._set()` triggers all the feature callbacks
155+
coll_feature._set(new_data)
156+
143157
def _reset_feature(self, feature: str):
144158
if feature not in self._previous_data.keys():
145159
return

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy