8
8
9
9
__author__ = "Jonas Van Der Donckt, Jeroen Van Der Donckt, Emiel Deprost"
10
10
11
+ import itertools
11
12
import re
12
13
from abc import ABC
13
14
from collections import namedtuple
@@ -393,12 +394,37 @@ def _nest_dict_rec(k: str, v: any, out: dict) -> None:
393
394
394
395
return trace
395
396
397
+ def _layout_xaxis_to_trace_xaxis_mapping (self ) -> Dict [str , List [str ]]:
398
+ """Construct a dict which maps the layout xaxis keys to the trace xaxis keys.
399
+
400
+ Returns
401
+ -------
402
+ Dict[str, List[str]]
403
+ A dict with the layout xaxis values as keys and the trace its corresponding
404
+ xaxis anchor value.
405
+
406
+ """
407
+ # edge case: an empty `go.Figure()` does not yet contain axes keys
408
+ if self ._grid_ref is None :
409
+ return {"xaxis" : ["x" ]}
410
+
411
+ mapping_dict = {}
412
+ for sub_plot in itertools .chain .from_iterable (self ._grid_ref ): # flattten
413
+ sub_plot = [] if sub_plot is None else sub_plot
414
+ for axes in sub_plot : # NOTE: you can have multiple axes in a subplot
415
+ layout_xaxes = axes .layout_keys [0 ]
416
+ trace_xaxes = axes .trace_kwargs ["xaxis" ]
417
+
418
+ # append the trace xaxis to the layout xaxis key its value list
419
+ mapping_dict .setdefault (layout_xaxes , []).append (trace_xaxes )
420
+ return mapping_dict
421
+
396
422
def _check_update_figure_dict (
397
423
self ,
398
424
figure : dict ,
399
425
start : Optional [Union [float , str ]] = None ,
400
426
stop : Optional [Union [float , str ]] = None ,
401
- xaxis_filter : str = None ,
427
+ layout_xaxis_filter : Optional [ str ] = None ,
402
428
updated_trace_indices : Optional [List [int ]] = None ,
403
429
) -> List [int ]:
404
430
"""Check and update the traces within the figure dict.
@@ -421,8 +447,9 @@ def _check_update_figure_dict(
421
447
The start time for the new resampled data view, by default None.
422
448
stop : Union[float, str], optional
423
449
The end time for the new resampled data view, by default None.
424
- xaxis_filter: str, optional
425
- Additional trace-update subplot filter, by default None.
450
+ layout_xaxis_filter: str, optional
451
+ Additional layout xaxis filter, e.g. the affected x-axis values by the
452
+ triggered relayout event (e.g. xaxis), by default None.
426
453
updated_trace_indices: List[int], optional
427
454
List of trace indices that already have been updated, by default None.
428
455
@@ -433,71 +460,23 @@ def _check_update_figure_dict(
433
460
modalities which are updated.
434
461
435
462
"""
436
- xaxis_filter_short = None
437
- if xaxis_filter is not None :
438
- xaxis_filter_short = "x" + xaxis_filter .lstrip ("xaxis" )
439
-
440
463
if updated_trace_indices is None :
441
464
updated_trace_indices = []
442
465
466
+ if layout_xaxis_filter is not None :
467
+ layout_trace_mapping = self ._layout_xaxis_to_trace_xaxis_mapping ()
468
+ # Retrieve the trace xaxis values that are affected by the relayout event
469
+ trace_xaxis_filter : List [str ] = layout_trace_mapping [layout_xaxis_filter ]
470
+
443
471
for idx , trace in enumerate (figure ["data" ]):
444
- # We skip when the trace-idx already has been updated.
445
- if idx in updated_trace_indices :
472
+ # We skip when (i) the trace-idx already has been updated or (ii) when
473
+ # there is a layout_xaxis_filter and the trace xaxis is not in the filter
474
+ if idx in updated_trace_indices or (
475
+ layout_xaxis_filter is not None
476
+ and trace .get ("xaxis" , "x" ) not in trace_xaxis_filter
477
+ ):
446
478
continue
447
479
448
- if xaxis_filter is not None :
449
- # the x-anchor of the trace is stored in the layout data
450
- if trace .get ("yaxis" ) is None :
451
- # TODO In versions up until v0.8.2 we made the assumption that yaxis
452
- # = xaxis_filter_short. -> Why did we make this assumption?
453
- y_axis = "y" # + xaxis_filter[1:]
454
- else :
455
- y_axis = "yaxis" + trace .get ("yaxis" )[1 :]
456
-
457
- # Also check for overlaying traces - fixes #242
458
- overlaying = figure ["layout" ].get (y_axis , {}).get ("overlaying" )
459
- if overlaying :
460
- y_axis = "yaxis" + overlaying [1 :]
461
-
462
- # Next to the x-anchor, we also fetch the xaxis which matches the
463
- # current trace (i.e. if this value is not None, the axis shares the
464
- # x-axis with one or more traces).
465
- # This is relevant when e.g. fig.update_traces(xaxis='x...') was called.
466
- x_anchor_trace = figure ["layout" ].get (y_axis , {}).get ("anchor" )
467
- if x_anchor_trace is not None :
468
- xaxis_matches = (
469
- figure ["layout" ]
470
- .get ("xaxis" + x_anchor_trace .lstrip ("x" ), {})
471
- .get ("matches" )
472
- )
473
- else :
474
- xaxis_matches = figure ["layout" ].get ("xaxis" , {}).get ("matches" )
475
-
476
- # print(
477
- # f"x_anchor: {x_anchor_trace} - xaxis_filter: {xaxis_filter} ",
478
- # f"- xaxis_matches: {xaxis_matches}"
479
- # )
480
-
481
- # We skip when:
482
- # * the change was made on the first row and the trace its anchor is not
483
- # in [None, 'x'] and the matching (a.k.a. shared) xaxis is not equal
484
- # to the xaxis filter argument.
485
- # -> why None: traces without row/col argument and stand on first row
486
- # and do not have the anchor property (hence the DICT.get() method)
487
- # * x_axis_filter_short not in [x_anchor or xaxis matches] for
488
- # NON first rows
489
- if (
490
- xaxis_filter_short == "x"
491
- and (
492
- x_anchor_trace not in (None , "x" )
493
- and xaxis_matches != xaxis_filter_short
494
- )
495
- ) or (
496
- xaxis_filter_short != "x"
497
- and (xaxis_filter_short not in (x_anchor_trace , xaxis_matches ))
498
- ):
499
- continue
500
-
501
480
# If we managed to find and update the trace, it will return the trace
502
481
# and thus not None.
503
482
updated_trace = self ._check_update_trace_data (trace , start = start , end = stop )
@@ -1361,7 +1340,7 @@ def construct_update_data(
1361
1340
current_graph ,
1362
1341
start = relayout_data [t_start_key ],
1363
1342
stop = relayout_data [t_stop_key ],
1364
- xaxis_filter = xaxis ,
1343
+ layout_xaxis_filter = xaxis ,
1365
1344
updated_trace_indices = updated_trace_indices ,
1366
1345
)
1367
1346
@@ -1377,7 +1356,7 @@ def construct_update_data(
1377
1356
xaxis = autorange_key .split ("." )[0 ]
1378
1357
updated_trace_indices = self ._check_update_figure_dict (
1379
1358
current_graph ,
1380
- xaxis_filter = xaxis ,
1359
+ layout_xaxis_filter = xaxis ,
1381
1360
updated_trace_indices = updated_trace_indices ,
1382
1361
)
1383
1362
# 2.1. Autorange -> do nothing, the autorange will be applied on the
0 commit comments