@@ -38,6 +38,15 @@ class VolumeSlicer:
38
38
style ``display: none``.
39
39
* ``stores``: a list of dcc.Store objects.
40
40
41
+ To programatically set the position of the slicer, use a store with
42
+ a dictionary-id with the following fields:
43
+
44
+ * 'context': a unique name for this store.
45
+ * 'scene': the scene_id for which to set the position
46
+ * 'name': 'setpos'
47
+
48
+ The value in the store must be an 3-element tuple (x, y, z) in scene coordinates.
49
+ To apply the position for one position only, use e.g ``(None, None, x)``.
41
50
"""
42
51
43
52
_global_slicer_counter = 0
@@ -72,6 +81,9 @@ def __init__(
72
81
raise ValueError ("The given axis must be 0, 1, or 2." )
73
82
self ._axis = int (axis )
74
83
self ._reverse_y = bool (reverse_y )
84
+ # Select the *other* axii
85
+ self ._other_axii = [0 , 1 , 2 ]
86
+ self ._other_axii .pop (self ._axis )
75
87
76
88
# Check and store scene id, and generate
77
89
if scene_id is None :
@@ -192,19 +204,21 @@ def create_overlay_data(self, mask, color=(0, 255, 255, 100)):
192
204
193
205
return overlay_slices
194
206
195
- def _subid (self , name , use_dict = False ):
207
+ def _subid (self , name , use_dict = False , ** kwargs ):
196
208
"""Given a name, get the full id including the context id prefix."""
197
209
if use_dict :
198
210
# A dict-id is nice to query objects with pattern matching callbacks,
199
211
# and we use that to show the position of other sliders. But it makes
200
212
# the id's very long, which is annoying e.g. in the callback graph.
201
- return {
213
+ d = {
202
214
"context" : self ._context_id ,
203
215
"scene" : self ._scene_id ,
204
- "axis" : self ._axis ,
205
216
"name" : name ,
206
217
}
218
+ d .update (kwargs )
219
+ return d
207
220
else :
221
+ assert not kwargs
208
222
return self ._context_id + "-" + name
209
223
210
224
def _slice (self , index ):
@@ -230,7 +244,8 @@ def _create_dash_components(self):
230
244
self ._fig = fig = Figure (data = [])
231
245
fig .update_layout (
232
246
template = None ,
233
- margin = dict (l = 0 , r = 0 , b = 0 , t = 0 , pad = 4 ),
247
+ margin = {"l" : 0 , "r" : 0 , "b" : 0 , "t" : 0 , "pad" : 4 },
248
+ dragmode = "pan" , # good default mode
234
249
)
235
250
fig .update_xaxes (
236
251
showgrid = False ,
@@ -265,7 +280,10 @@ def _create_dash_components(self):
265
280
266
281
# Create the stores that we need (these must be present in the layout)
267
282
self ._info = Store (id = self ._subid ("info" ), data = info )
268
- self ._position = Store (id = self ._subid ("position" , True ), data = 0 )
283
+ self ._position = Store (
284
+ id = self ._subid ("position" , True , axis = self ._axis ), data = 0
285
+ )
286
+ self ._setpos = Store (id = self ._subid ("setpos" , True ), data = None )
269
287
self ._requested_index = Store (id = self ._subid ("req-index" ), data = 0 )
270
288
self ._request_data = Store (id = self ._subid ("req-data" ), data = "" )
271
289
self ._lowres_data = Store (id = self ._subid ("lowres" ), data = thumbnails )
@@ -275,6 +293,7 @@ def _create_dash_components(self):
275
293
self ._stores = [
276
294
self ._info ,
277
295
self ._position ,
296
+ self ._setpos ,
278
297
self ._requested_index ,
279
298
self ._request_data ,
280
299
self ._lowres_data ,
@@ -299,6 +318,58 @@ def _create_client_callbacks(self):
299
318
"""Create the callbacks that run client-side."""
300
319
app = self ._app
301
320
321
+ # ----------------------------------------------------------------------
322
+ # Callback to trigger fellow slicers to go to a specific position.
323
+
324
+ app .clientside_callback (
325
+ """
326
+ function trigger_setpos(data, index, info) {
327
+ if (data && data.points && data.points.length) {
328
+ let point = data["points"][0];
329
+ let xyz = [point["x"], point["y"]];
330
+ let depth = info.origin[2] + index * info.spacing[2];
331
+ xyz.splice(2 - info.axis, 0, depth);
332
+ return xyz;
333
+ }
334
+ return dash_clientside.no_update;
335
+ }
336
+ """ ,
337
+ Output (self ._setpos .id , "data" ),
338
+ [Input (self ._graph .id , "clickData" )],
339
+ [State (self ._slider .id , "value" ), State (self ._info .id , "data" )],
340
+ )
341
+
342
+ # ----------------------------------------------------------------------
343
+ # Callback to update index from external setpos signal.
344
+
345
+ app .clientside_callback (
346
+ """
347
+ function respond_to_setpos(positions, cur_index, info) {
348
+ for (let trigger of dash_clientside.callback_context.triggered) {
349
+ if (!trigger.value) continue;
350
+ let pos = trigger.value[2 - info.axis];
351
+ if (typeof pos !== 'number') continue;
352
+ let index = Math.round((pos - info.origin[2]) / info.spacing[2]);
353
+ if (index == cur_index) continue;
354
+ return Math.max(0, Math.min(info.size[2] - 1, index));
355
+ }
356
+ return dash_clientside.no_update;
357
+ }
358
+ """ ,
359
+ Output (self ._slider .id , "value" ),
360
+ [
361
+ Input (
362
+ {
363
+ "scene" : self ._scene_id ,
364
+ "context" : ALL ,
365
+ "name" : "setpos" ,
366
+ },
367
+ "data" ,
368
+ )
369
+ ],
370
+ [State (self ._slider .id , "value" ), State (self ._info .id , "data" )],
371
+ )
372
+
302
373
# ----------------------------------------------------------------------
303
374
# Callback to update position (in scene coordinates) from the index.
304
375
@@ -309,7 +380,7 @@ def _create_client_callbacks(self):
309
380
}
310
381
""" ,
311
382
Output (self ._position .id , "data" ),
312
- [Input (self .slider .id , "value" )],
383
+ [Input (self ._slider .id , "value" )],
313
384
[State (self ._info .id , "data" )],
314
385
)
315
386
@@ -331,7 +402,7 @@ def _create_client_callbacks(self):
331
402
if (slice_cache[index]) {
332
403
return window.dash_clientside.no_update;
333
404
} else {
334
- console.log('request slice ' + index);
405
+ console.log('requesting slice ' + index);
335
406
return index;
336
407
}
337
408
}
@@ -416,10 +487,6 @@ def _create_client_callbacks(self):
416
487
# ----------------------------------------------------------------------
417
488
# Callback to create scatter traces from the positions of other slicers.
418
489
419
- # Select the *other* axii
420
- axii = [0 , 1 , 2 ]
421
- axii .pop (self ._axis )
422
-
423
490
# Create a callback to create a trace representing all slice-indices that:
424
491
# * corresponding to the same volume data
425
492
# * match any of the selected axii
@@ -464,7 +531,7 @@ def _create_client_callbacks(self):
464
531
},
465
532
"data" ,
466
533
)
467
- for axis in axii
534
+ for axis in self . _other_axii
468
535
],
469
536
[
470
537
State (self ._info .id , "data" ),
@@ -488,6 +555,7 @@ def _create_client_callbacks(self):
488
555
console.log("updating figure");
489
556
let figure = {...ori_figure};
490
557
figure.data = traces;
558
+
491
559
return figure;
492
560
}
493
561
""" ,
0 commit comments