@@ -90,6 +90,7 @@ def __init__(self, server, source):
9090 self ._var2view = {}
9191 self ._last_vars = {}
9292 self ._active_configs = {}
93+ self ._group_orders = {}
9394
9495 rca .initialize (self .server )
9596 colormaps .initialize (self .server )
@@ -402,8 +403,7 @@ def swap_pair(array_name_a, array_name_b):
402403
403404 metadata_a = self .source .data_reader .get_array_metadata (variable_a ) or {}
404405 metadata_b = self .source .data_reader .get_array_metadata (variable_b ) or {}
405- grouped_layout = self .state .layout_grouped
406- if grouped_layout and self .state .comparison_mode == "multi-sim" :
406+ if self .state .comparison_mode == "multi-sim" :
407407 path_a = metadata_a .get ("path" )
408408 path_b = metadata_b .get ("path" )
409409
@@ -426,10 +426,6 @@ def swap_pair(array_name_a, array_name_b):
426426 self .state .simulation_configs = simulation_configs
427427 return
428428
429- if not grouped_layout :
430- swap_pair (variable_a , variable_b )
431- return
432-
433429 base_variable_a = metadata_a .get ("base_variable" )
434430 base_variable_b = metadata_b .get ("base_variable" )
435431 slot_a = None
@@ -466,25 +462,37 @@ def swap_pair(array_name_a, array_name_b):
466462 view_specs [slot_b ]["array_name" ],
467463 )
468464
465+ @controller .set ("swap_variable_groups" )
466+ def swap_variable_groups (self , variable_a , variable_b ):
467+ if not variable_a or not variable_b or variable_a == variable_b :
468+ return
469+
470+ if variable_a not in self ._group_orders or variable_b not in self ._group_orders :
471+ return
472+
473+ self ._group_orders [variable_a ], self ._group_orders [variable_b ] = (
474+ self ._group_orders [variable_b ],
475+ self ._group_orders [variable_a ],
476+ )
477+
478+ if self ._last_vars :
479+ self .build_auto_layout (self ._last_vars )
480+ self .render ()
481+
469482 def apply_size (self , n_cols ):
470483 if not self ._last_vars :
471484 return
472485
473486 if n_cols == 0 :
474487 # Auto size views based on the number of comparison panels being shown.
475- if self .state .layout_grouped :
476- for var_type , var_names in self ._last_vars .items ():
477- for var_name in var_names :
478- view_specs = self .get_view_specs (var_name )
479- if not view_specs :
480- continue
481- size = auto_size_to_col (len (view_specs ))
482- for view_spec in view_specs :
483- self .get_view (view_spec , var_type ).config .size = size
484- else :
485- size = auto_size_to_col (len (self ._active_configs ))
486- for config in self ._active_configs .values ():
487- config .size = size
488+ for var_type , var_names in self ._last_vars .items ():
489+ for var_name in var_names :
490+ view_specs = self .get_view_specs (var_name )
491+ if not view_specs :
492+ continue
493+ size = auto_size_to_col (len (view_specs ))
494+ for view_spec in view_specs :
495+ self .get_view (view_spec , var_type ).config .size = size
488496 else :
489497 # Apply a uniform size to all active views.
490498 for config in self ._active_configs .values ():
@@ -505,113 +513,89 @@ def build_auto_layout(self, variables=None):
505513 export_items = []
506514 # Build a lookup from variable type to the matching group border color.
507515 type_to_color = {vt ["name" ]: vt ["color" ] for vt in self .state .variable_types }
516+ flat_vars = []
517+ for var_type , var_names in variables .items ():
518+ for var_name in var_names :
519+ view_specs = self .get_view_specs (var_name )
520+ if not view_specs :
521+ continue
522+ flat_vars .append ((var_type , var_name , view_specs , self ._group_orders .get (var_name )))
523+
524+ current_group_order = {var_name : saved for _ , var_name , _ , saved in flat_vars if saved is not None }
525+ next_group_order_idx = (max (current_group_order .values ()) + 1 ) if current_group_order else 1
526+ for _ , var_name , _ , saved in flat_vars :
527+ if saved is None :
528+ current_group_order [var_name ] = next_group_order_idx
529+ next_group_order_idx += 1
530+
531+ self ._group_orders = current_group_order
532+
533+ grouped_entries = sorted (
534+ (
535+ (current_group_order [var_name ], var_type , var_name , view_specs )
536+ for var_type , var_name , view_specs , _ in flat_vars
537+ ),
538+ key = lambda item : item [0 ],
539+ )
540+
508541 with DivLayout (self .server , template_name = "auto_layout" ) as self .ui :
509- if self . state . layout_grouped :
510- with v3 . VCol ( classes = "pa-1" ):
511- for var_type , var_names in variables . items ( ):
512- for var_name in var_names :
513- view_specs = self . get_view_specs ( var_name )
514- if not view_specs :
515- continue
516-
517- type_name = (
518- " , ". join ( var_type )
519- if isinstance ( var_type , ( list , tuple ))
520- else str ( var_type )
521- )
522- border_color = type_to_color . get ( type_name , "primary" )
523- with v3 . VAlert (
524- border = "start " ,
525- classes = "pr-1 py-1 pl-3 mb-6" ,
526- variant = "flat" ,
527- border_color = border_color ,
528- ):
529- html . Div (
530- var_name ,
531- classes = "text-subtitle-2 font-weight-medium mb-1" ,
532- )
533- with v3 . VRow ( dense = True ):
534- use_config_size = (
535- self . state . comparison_mode == "multi-sim"
536- )
537- if not use_config_size :
538- views_per_row = max ( 1 , len ( view_specs ))
539- group_cols = max (
540- 1 , math . floor ( 12 / views_per_row )
541- )
542- group_swap_items = [
543- {
544- "name" : view_spec [ "array_name" ],
545- "label" : view_spec . get (
546- "label" , view_spec [ "array_name" ]
542+ group_names = [ var_name for _ , _ , var_name , _ in grouped_entries ]
543+
544+ with v3 . VCol ( classes = "pa-1" ):
545+ for _ , var_type , var_name , view_specs in grouped_entries :
546+ type_name = (
547+ ", " . join ( var_type )
548+ if isinstance ( var_type , ( list , tuple ))
549+ else str ( var_type )
550+ )
551+ border_color = type_to_color . get ( type_name , "primary" )
552+ with v3 . VAlert (
553+ border = "start" ,
554+ classes = "pr-1 py-1 pl-3 mb-6" ,
555+ variant = "flat" ,
556+ border_color = border_color ,
557+ key = f"group- { var_name } " ,
558+ ):
559+ with html . Div (
560+ var_name ,
561+ classes = (
562+ "text-subtitle-2 "
563+ "font-weight-medium mb-1 d-inline-block"
564+ ) ,
565+ style = "user-select: none; cursor: pointer;" ,
566+ ):
567+ with v3 . VMenu ( activator = "parent" ):
568+ with v3 . VList (
569+ density = "compact" ,
570+ style = "max-height: 40vh;" ,
571+ ):
572+ for swap_name in group_names :
573+ if swap_name == var_name :
574+ continue
575+ v3 . VListItem (
576+ title = swap_name ,
577+ click = (
578+ self . ctrl . swap_variable_groups ,
579+ f"[' { var_name } ', ' { swap_name } ']" ,
547580 ),
548- }
549- for view_spec in view_specs
550- ]
551- for view_spec in view_specs :
552- view = self .get_view (view_spec , var_type )
553- export_items .append (
554- {
555- "title" : view_spec .get (
556- "label" , view_spec ["array_name" ]
557- ),
558- "value" : view_spec ["array_name" ],
559- }
560581 )
561- view .config .swap_group = sorted (
562- [
563- item
564- for item in group_swap_items
565- if item ["name" ]
566- != view_spec ["array_name" ]
567- ],
568- key = lambda item : item ["name" ],
569- )
570- with view .config .provide_as ("config" ):
571- v3 .VCol (
572- v_if = "config.break_row" ,
573- cols = 12 ,
574- classes = "pa-0" ,
575- style = ("`order: ${config.order};`" ,),
576- )
577- # For flow handling
578- with v3 .Template (v_if = "!config.size" ):
579- v3 .VCol (
580- v_for = "i in config.offset" ,
581- key = "i" ,
582- style = ("{ order: config.order }" ,),
583- )
584- with v3 .VCol (
585- offset = (
586- "config.size ? config.offset * config.size : 0" ,
587- )
588- if use_config_size
589- else ("config.offset * config.size" ,),
590- cols = ("config.size" ,)
591- if use_config_size
592- else group_cols ,
593- style = ("`order: ${config.order};`" ,),
594- ):
595- client .ServerTemplate (name = view .name )
596- else :
597- all_swap_items = []
598- for var_name_list in variables .values ():
599- for var_name in var_name_list :
600- all_swap_items .extend (
601- [
582+ with v3 .VRow (dense = True ):
583+ use_config_size = (
584+ self .state .comparison_mode == "multi-sim"
585+ )
586+ if not use_config_size :
587+ views_per_row = max (1 , len (view_specs ))
588+ group_cols = max (
589+ 1 , math .floor (12 / views_per_row )
590+ )
591+ panel_options = [
602592 {
603- "name" : view_spec ["array_name" ],
604- "label" : view_spec .get (
605- "label" , view_spec ["array_name" ]
606- ),
593+ "name" : vs ["array_name" ],
594+ "label" : vs .get ("label" , vs ["array_name" ]),
607595 }
608- for view_spec in self . get_view_specs ( var_name )
596+ for vs in view_specs
609597 ]
610- )
611- with v3 .VRow (dense = True , classes = "pa-2" ):
612- for var_type , var_names in variables .items ():
613- for name in var_names :
614- for view_spec in self .get_view_specs (name ):
598+ for view_spec in view_specs :
615599 view = self .get_view (view_spec , var_type )
616600 export_items .append (
617601 {
@@ -624,7 +608,7 @@ def build_auto_layout(self, variables=None):
624608 view .config .swap_group = sorted (
625609 [
626610 item
627- for item in all_swap_items
611+ for item in panel_options
628612 if item ["name" ] != view_spec ["array_name" ]
629613 ],
630614 key = lambda item : item ["name" ],
@@ -636,7 +620,6 @@ def build_auto_layout(self, variables=None):
636620 classes = "pa-0" ,
637621 style = ("`order: ${config.order};`" ,),
638622 )
639-
640623 # For flow handling
641624 with v3 .Template (v_if = "!config.size" ):
642625 v3 .VCol (
@@ -647,39 +630,40 @@ def build_auto_layout(self, variables=None):
647630 with v3 .VCol (
648631 offset = (
649632 "config.size ? config.offset * config.size : 0" ,
650- ),
651- cols = ("config.size" ,),
633+ )
634+ if use_config_size
635+ else ("config.offset * config.size" ,),
636+ cols = ("config.size" ,)
637+ if use_config_size
638+ else group_cols ,
652639 style = ("`order: ${config.order};`" ,),
640+ key = view_spec ["array_name" ],
653641 ):
654642 client .ServerTemplate (name = view .name )
655643
656644 self .state .animation_export_items = export_items
657645
658- # Assign any missing order.
659646 self ._active_configs = {}
660- existed_order = set ()
661- order_max = 0
662- orders_to_update = []
663- for var_type , var_names in variables .items ():
664- for var_name in var_names :
665- for view_spec in self .get_view_specs (var_name ):
666- config = self .get_view (view_spec , var_type ).config
667- name = view_spec ["array_name" ]
668- self ._active_configs [name ] = config
669- if config .order :
670- if config .order in existed_order :
671- config .order = 0
672- orders_to_update .append (config )
673- continue
674- order_max = max (order_max , config .order )
675- existed_order .add (config .order )
676- else :
677- orders_to_update .append (config )
647+ next_order_idx = 1
648+ for _ , var_type , _ , view_specs in grouped_entries :
649+ view_items = [
650+ (index , view_spec , self .get_view (view_spec , var_type ))
651+ for index , view_spec in enumerate (view_specs )
652+ ]
653+ if self .state .comparison_mode != "multi-sim" :
654+ view_items = sorted (
655+ view_items ,
656+ key = lambda item : (
657+ item [2 ].config .order or len (view_specs ) + item [0 ],
658+ item [0 ],
659+ ),
660+ )
678661
679- next_order = order_max + 1
680- for config in orders_to_update :
681- config .order = next_order
682- next_order += 1
662+ for _ , view_spec , view in view_items :
663+ config = view .config
664+ config .order = next_order_idx
665+ self ._active_configs [view_spec ["array_name" ]] = config
666+ next_order_idx += 1
683667
684668 self .layout_dirty = True
685669 self .compute_layout ()
0 commit comments