1414from e3sm_quickview .assets import ASSETS
1515from e3sm_quickview .components import doc , file_browser , css , toolbars , dialogs , drawers
1616from e3sm_quickview .pipeline import EAMVisSource
17- from e3sm_quickview .utils import compute , js , constants , cli
17+ from e3sm_quickview .utils import compute , cli
1818from e3sm_quickview .view_manager import ViewManager
1919
2020
@@ -45,13 +45,11 @@ def __init__(self, server=None):
4545 "variables_selected" : [],
4646 # Control 'Load Variables' button availability
4747 "variables_loaded" : False ,
48- # Level controls
49- "midpoint_idx" : 0 ,
48+ # Dynamic type-color mapping (populated when data loads)
49+ "variable_types" : [],
50+ # Dimension arrays (will be populated dynamically)
5051 "midpoints" : [],
51- "interface_idx" : 0 ,
5252 "interfaces" : [],
53- # Time controls
54- "time_idx" : 0 ,
5553 "timestamps" : [],
5654 # Fields summaries
5755 "fields_avgs" : {},
@@ -208,18 +206,19 @@ def _build_ui(self, **_):
208206
209207 @property
210208 def selected_variables (self ):
211- vars_per_type = {n : [] for n in "smi" }
209+ from collections import defaultdict
210+
211+ vars_per_type = defaultdict (list )
212212 for var in self .state .variables_selected :
213- type = var [0 ]
214- name = var [1 :]
215- vars_per_type [type ].append (name )
213+ type = self .source .varmeta [var ].dimensions
214+ vars_per_type [type ].append (var )
216215
217- return vars_per_type
216+ return dict ( vars_per_type )
218217
219218 @property
220219 def selected_variable_names (self ):
221220 # Remove var type (first char)
222- return [var [ 1 :] for var in self .state .variables_selected ]
221+ return [var for var in self .state .variables_selected ]
223222
224223 # -------------------------------------------------------------------------
225224 # Methods connected to UI
@@ -369,30 +368,42 @@ async def data_loading_open(self, simulation, connectivity):
369368 self .state .variables_filter = ""
370369 self .state .variables_listing = [
371370 * (
372- {"name" : name , "type" : "surface" , "id" : f"s{ name } " }
373- for name in self .source .surface_vars
374- ),
375- * (
376- {"name" : name , "type" : "interface" , "id" : f"i{ name } " }
377- for name in self .source .interface_vars
378- ),
379- * (
380- {"name" : name , "type" : "midpoint" , "id" : f"m{ name } " }
381- for name in self .source .midpoint_vars
371+ {
372+ "name" : var .name ,
373+ "type" : str (var .dimensions ),
374+ "id" : f"{ var .name } " ,
375+ }
376+ for _ , var in self .source .varmeta .items ()
382377 ),
383378 ]
384379
380+ # Build dynamic type-color mapping
381+ from e3sm_quickview .utils .colors import get_type_color
382+
383+ dim_types = sorted (
384+ set (str (var .dimensions ) for var in self .source .varmeta .values ())
385+ )
386+ self .state .variable_types = [
387+ {"name" : t , "color" : get_type_color (i )}
388+ for i , t in enumerate (dim_types )
389+ ]
390+
385391 # Update Layer/Time values and ui layout
386392 n_cols = 0
387393 available_tracks = []
388- for name in ["midpoints" , "interfaces" , "timestamps" ]:
389- values = getattr (self .source , name )
390- self .state [name ] = values
391-
392- if len (values ) > 1 :
394+ for name , dim in self .source .dimmeta .items ():
395+ values = dim .data
396+ # Convert to list for JSON serialization
397+ self .state [name ] = (
398+ values .tolist ()
399+ if hasattr (values , "tolist" )
400+ else list (values )
401+ if values is not None
402+ else []
403+ )
404+ if values is not None and len (values ) > 1 :
393405 n_cols += 1
394- available_tracks .append (constants .TRACK_ENTRIES [name ])
395-
406+ available_tracks .append ({"title" : name , "value" : name })
396407 self .state .toolbar_slider_cols = 12 / n_cols if n_cols else 12
397408 self .state .animation_tracks = available_tracks
398409 self .state .animation_track = (
@@ -401,6 +412,20 @@ async def data_loading_open(self, simulation, connectivity):
401412 else None
402413 )
403414
415+ from functools import partial
416+
417+ # Initialize dynamic index variables for each dimension
418+ for track in available_tracks :
419+ dim_name = track ["value" ]
420+ index_var = f"{ dim_name } _idx"
421+ if "time" in index_var :
422+ self .state [index_var ] = 50
423+ else :
424+ self .state [index_var ] = 0
425+ self .state .change (index_var )(
426+ partial (self ._on_slicing_change , dim_name , index_var )
427+ )
428+
404429 @controller .set ("file_selection_cancel" )
405430 def data_loading_hide (self ):
406431 self .state .active_tools = [
@@ -414,11 +439,10 @@ async def _data_load_variables(self):
414439 """Called at 'Load Variables' button click"""
415440 vars_to_show = self .selected_variables
416441
417- self .source .LoadVariables (
418- vars_to_show ["s" ], # surfaces
419- vars_to_show ["m" ], # midpoints
420- vars_to_show ["i" ], # interfaces
421- )
442+ # Flatten the list of lists
443+ flattened_vars = [var for var_list in vars_to_show .values () for var in var_list ]
444+
445+ self .source .LoadVariables (flattened_vars )
422446
423447 # Trigger source update + compute avg
424448 with self .state :
@@ -455,30 +479,42 @@ async def _on_projection(self, projection, **_):
455479 await asyncio .sleep (0.1 )
456480 self .view_manager .reset_camera ()
457481
458- @change ("active_tools" )
482+ @change ("active_tools" , "animation_tracks" )
459483 def _on_toolbar_change (self , active_tools , ** _ ):
460484 top_padding = 0
461485 for name in active_tools :
462- top_padding += toolbars .SIZES .get (name , 0 )
486+ if name == "select-slice-time" :
487+ track_count = len (self .state .animation_tracks or [])
488+ rows_needed = max (1 , (track_count + 2 ) // 3 ) # 3 sliders per row
489+ top_padding += 70 * rows_needed
490+ else :
491+ top_padding += toolbars .SIZES .get (name , 0 )
463492
464493 self .state .top_padding = top_padding
465494
495+ def _on_slicing_change (self , var , ind_var , ** _ ):
496+ self .source .UpdateSlicing (var , self .state [ind_var ])
497+ self .source .UpdatePipeline ()
498+
499+ self .view_manager .update_color_range ()
500+ self .view_manager .render ()
501+
502+ # Update avg computation
503+ # Get area variable to calculate weighted average
504+ data = self .source .views ["atmosphere_data" ]
505+ self .state .fields_avgs = compute .extract_avgs (
506+ data , self .selected_variable_names
507+ )
508+
466509 @change (
467510 "variables_loaded" ,
468- "time_idx" ,
469- "midpoint_idx" ,
470- "interface_idx" ,
471511 "crop_longitude" ,
472512 "crop_latitude" ,
473513 "projection" ,
474514 )
475- def _on_time_change (
515+ def _on_downstream_change (
476516 self ,
477517 variables_loaded ,
478- time_idx ,
479- timestamps ,
480- midpoint_idx ,
481- interface_idx ,
482518 crop_longitude ,
483519 crop_latitude ,
484520 projection ,
@@ -487,12 +523,9 @@ def _on_time_change(
487523 if not variables_loaded :
488524 return
489525
490- time_value = timestamps [time_idx ] if len (timestamps ) else 0.0
491- self .source .UpdateLev (midpoint_idx , interface_idx )
492526 self .source .ApplyClipping (crop_longitude , crop_latitude )
493527 self .source .UpdateProjection (projection [0 ])
494- self .source .UpdateTimeStep (time_idx )
495- self .source .UpdatePipeline (time_value )
528+ self .source .UpdatePipeline ()
496529
497530 self .view_manager .update_color_range ()
498531 self .view_manager .render ()
0 commit comments