@@ -44,39 +44,37 @@ def validate(
44
44
) -> Union [Tuple [int , ...], List [int ], pa .ChunkedArray , pa .FixedSizeListArray ]:
45
45
if isinstance (value , np .ndarray ):
46
46
if value .ndim != 2 :
47
- self .error (obj , value , info = "Point array must have 2 dimensions. " )
47
+ self .error (obj , value , info = "Point array to have 2 dimensions" )
48
48
49
49
list_size = value .shape [1 ]
50
50
if list_size not in (2 , 3 ):
51
51
self .error (
52
52
obj ,
53
53
value ,
54
- info = "Point array must have 2 or 3 as its second dimension. " ,
54
+ info = "Point array to have 2 or 3 as its second dimension" ,
55
55
)
56
56
57
57
return pa .FixedSizeListArray .from_arrays (value .flatten ("C" ), list_size )
58
58
59
59
if isinstance (value , (pa .ChunkedArray , pa .Array )):
60
60
if not pa .types .is_fixed_size_list (value .type ):
61
- self .error (
62
- obj , value , info = "Point pyarrow array must be a FixedSizeList."
63
- )
61
+ self .error (obj , value , info = "Point pyarrow array to be a FixedSizeList" )
64
62
65
63
if value .type .list_size not in (2 , 3 ):
66
64
self .error (
67
65
obj ,
68
66
value ,
69
67
info = (
70
- "Color pyarrow array must have a FixedSizeList inner size of "
71
- "2 or 3. "
68
+ "Color pyarrow array to be a FixedSizeList with list size of "
69
+ "2 or 3"
72
70
),
73
71
)
74
72
75
73
if not pa .types .is_floating (value .type .value_type ):
76
74
self .error (
77
75
obj ,
78
76
value ,
79
- info = "Point pyarrow array must have a floating point child. " ,
77
+ info = "Point pyarrow array to have a floating point child" ,
80
78
)
81
79
82
80
return value
@@ -89,8 +87,8 @@ def validate(
89
87
obj ,
90
88
value ,
91
89
info = (
92
- "Color string must be a hex string interpretable by "
93
- "matplotlib.colors.to_rgba. "
90
+ "Color string to be a hex string interpretable by "
91
+ "matplotlib.colors.to_rgba"
94
92
),
95
93
)
96
94
return
@@ -99,3 +97,191 @@ def validate(
99
97
100
98
self .error (obj , value )
101
99
assert False
100
+
101
+
102
+ class GetFilterValueAccessor (FixedErrorTraitType ):
103
+ """
104
+ A trait to validate input for the `get_filter_value` accessor added by the
105
+ [`DataFilterExtension`][lonboard.experimental.DataFilterExtension], which can have
106
+ between 1 and 4 float values per row.
107
+
108
+ Various input is allowed:
109
+
110
+ - An `int` or `float`. This will be used as the value for all objects. The
111
+ `filter_size` of the
112
+ [`DataFilterExtension`][lonboard.experimental.DataFilterExtension] instance must
113
+ be 1.
114
+ - A one-dimensional numpy `ndarray` with a numeric data type. This will be casted to
115
+ an array of data type [`np.float32`][numpy.float32]. Each value in the array will
116
+ be used as the value for the object at the same row index. The `filter_size` of
117
+ the [`DataFilterExtension`][lonboard.experimental.DataFilterExtension] instance
118
+ must be 1.
119
+ - A two-dimensional numpy `ndarray` with a numeric data type. This will be casted to
120
+ an array of data type [`np.float32`][numpy.float32]. Each value in the array will
121
+ be used as the value for the object at the same row index. The `filter_size` of
122
+ the [`DataFilterExtension`][lonboard.experimental.DataFilterExtension] instance
123
+ must match the size of the second dimension of the array.
124
+ - A pandas `Series` with a numeric data type. This will be casted to an array of
125
+ data type [`np.float32`][numpy.float32]. Each value in the array will be used as
126
+ the value for the object at the same row index. The `filter_size` of
127
+ the [`DataFilterExtension`][lonboard.experimental.DataFilterExtension] instance
128
+ must be 1.
129
+ - A pyarrow [`FloatArray`][pyarrow.FloatArray], [`DoubleArray`][pyarrow.DoubleArray]
130
+ or [`ChunkedArray`][pyarrow.ChunkedArray] containing either a `FloatArray` or
131
+ `DoubleArray`. Each value in the array will be used as the value for the object at
132
+ the same row index. The `filter_size` of the
133
+ [`DataFilterExtension`][lonboard.experimental.DataFilterExtension] instance must
134
+ be 1.
135
+
136
+ Alternatively, you can pass any corresponding Arrow data structure from a library
137
+ that implements the [Arrow PyCapsule
138
+ Interface](https://arrow.apache.org/docs/format/CDataInterface/PyCapsuleInterface.html).
139
+ - A pyarrow [`FixedSizeListArray`][pyarrow.FixedSizeListArray] or
140
+ [`ChunkedArray`][pyarrow.ChunkedArray] containing `FixedSizeListArray`s. The child
141
+ array of the fixed size list must be of floating point type. The `filter_size` of
142
+ the [`DataFilterExtension`][lonboard.experimental.DataFilterExtension] instance
143
+ must match the list size.
144
+
145
+ Alternatively, you can pass any corresponding Arrow data structure from a library
146
+ that implements the [Arrow PyCapsule
147
+ Interface](https://arrow.apache.org/docs/format/CDataInterface/PyCapsuleInterface.html).
148
+ """
149
+
150
+ default_value = float (0 )
151
+ info_text = (
152
+ "a float value or numpy ndarray or pyarrow array representing an array"
153
+ " of floats"
154
+ )
155
+
156
+ def __init__ (
157
+ self : TraitType ,
158
+ * args ,
159
+ ** kwargs : Any ,
160
+ ) -> None :
161
+ super ().__init__ (* args , ** kwargs )
162
+ self .tag (sync = True , ** ACCESSOR_SERIALIZATION )
163
+
164
+ def validate (self , obj , value ) -> Union [float , pa .ChunkedArray , pa .DoubleArray ]:
165
+ # Find the data filter extension in the attributes of the parent object so we
166
+ # can validate against the filter size.
167
+ data_filter_extension = [
168
+ ext for ext in obj .extensions if ext ._extension_type == "data-filter"
169
+ ]
170
+ assert len (data_filter_extension ) == 1
171
+ filter_size = data_filter_extension [0 ].filter_size
172
+
173
+ if isinstance (value , (int , float )):
174
+ if filter_size != 1 :
175
+ self .error (obj , value , info = "filter_size==1 with scalar value" )
176
+
177
+ return float (value )
178
+
179
+ if isinstance (value , (tuple , list )):
180
+ if filter_size != len (value ):
181
+ self .error (
182
+ obj ,
183
+ value ,
184
+ info = f"filter_size ({ filter_size } ) to match length of tuple/list" ,
185
+ )
186
+
187
+ if any (not isinstance (v , (int , float )) for v in value ):
188
+ self .error (
189
+ obj ,
190
+ value ,
191
+ info = "all values in tuple or list to be numeric" ,
192
+ )
193
+
194
+ return value
195
+
196
+ # pandas Series
197
+ if (
198
+ value .__class__ .__module__ .startswith ("pandas" )
199
+ and value .__class__ .__name__ == "Series"
200
+ ):
201
+ # Assert that filter_size == 1 for a pandas series.
202
+ # Pandas series can technically contain Python list objects inside them, but
203
+ # for simplicity we disallow that.
204
+ if filter_size != 1 :
205
+ self .error (obj , value , info = "filter_size==1 with pandas Series" )
206
+
207
+ # Cast pandas Series to numpy ndarray
208
+ value = np .asarray (value )
209
+
210
+ if isinstance (value , np .ndarray ):
211
+ if not np .issubdtype (value .dtype , np .number ):
212
+ self .error (obj , value , info = "numeric dtype" )
213
+
214
+ # Cast to float32
215
+ value = value .astype (np .float32 )
216
+
217
+ if len (value .shape ) == 1 :
218
+ if filter_size != 1 :
219
+ self .error (obj , value , info = "filter_size==1 with 1-D numpy array" )
220
+
221
+ return pa .array (value )
222
+
223
+ if len (value .shape ) != 2 :
224
+ self .error (obj , value , info = "1-D or 2-D numpy array" )
225
+
226
+ if value .shape [1 ] != filter_size :
227
+ self .error (
228
+ obj ,
229
+ value ,
230
+ info = (
231
+ f"filter_size ({ filter_size } ) to match 2nd dimension of "
232
+ "numpy array"
233
+ ),
234
+ )
235
+
236
+ return pa .FixedSizeListArray .from_arrays (value .flatten ("C" ), filter_size )
237
+
238
+ # Check for Arrow PyCapsule Interface
239
+ # https://arrow.apache.org/docs/format/CDataInterface/PyCapsuleInterface.html
240
+ # TODO: with pyarrow v16 also import chunked array from stream
241
+ if not isinstance (value , (pa .ChunkedArray , pa .Array )):
242
+ if hasattr (value , "__arrow_c_array__" ):
243
+ value = pa .array (value )
244
+
245
+ if isinstance (value , (pa .ChunkedArray , pa .Array )):
246
+ # Allowed inputs are either a FixedSizeListArray or numeric array.
247
+ # If not a fixed size list array, check for floating and cast to float32
248
+ if not pa .types .is_fixed_size_list (value .type ):
249
+ if filter_size != 1 :
250
+ self .error (
251
+ obj ,
252
+ value ,
253
+ info = "filter_size==1 with non-FixedSizeList type arrow array" ,
254
+ )
255
+
256
+ if not pa .types .is_floating (value .type ):
257
+ self .error (
258
+ obj ,
259
+ value ,
260
+ info = "arrow array to be a floating point type" ,
261
+ )
262
+
263
+ return value .cast (pa .float32 ())
264
+
265
+ # We have a FixedSizeListArray
266
+ if filter_size != value .type .list_size :
267
+ self .error (
268
+ obj ,
269
+ value ,
270
+ info = (
271
+ f"filter_size ({ filter_size } ) to match list size of "
272
+ "FixedSizeList arrow array"
273
+ ),
274
+ )
275
+
276
+ if not pa .types .is_floating (value .type .value_type ):
277
+ self .error (
278
+ obj ,
279
+ value ,
280
+ info = "arrow array to have floating point child type" ,
281
+ )
282
+
283
+ # Cast values to float32
284
+ return value .cast (pa .list_ (pa .float32 (), value .type .list_size ))
285
+
286
+ self .error (obj , value )
287
+ assert False
0 commit comments