Skip to content

Commit a8944a6

Browse files
authored
Merge pull request #6 from Kitware/general_slicing
Adding general slicing with arbitrary dimension data.
2 parents 7fada69 + 0de0666 commit a8944a6

File tree

9 files changed

+462
-495
lines changed

9 files changed

+462
-495
lines changed

src/e3sm_quickview/app.py

Lines changed: 81 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
from e3sm_quickview.assets import ASSETS
1515
from e3sm_quickview.components import doc, file_browser, css, toolbars, dialogs, drawers
1616
from e3sm_quickview.pipeline import EAMVisSource
17-
from e3sm_quickview.utils import compute, js, constants, cli
17+
from e3sm_quickview.utils import compute, cli
1818
from 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()

src/e3sm_quickview/components/drawers.py

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -68,20 +68,26 @@ def __init__(self, load_variables=None):
6868

6969
with self:
7070
with html.Div(style="position:fixed;top:0;width: 500px;"):
71-
with v3.VCardActions(key="variables_selected.length"):
72-
for name, color in [
73-
("surfaces", "success"),
74-
("interfaces", "info"),
75-
("midpoints", "warning"),
76-
]:
77-
v3.VChip(
78-
js.var_title(name),
79-
color=color,
80-
v_show=js.var_count(name),
81-
size="small",
82-
closable=True,
83-
click_close=js.var_remove(name),
84-
)
71+
with v3.VCardActions(
72+
key="variables_selected.length",
73+
classes="flex-wrap",
74+
style="overflow-y: auto; max-height: 100px;",
75+
):
76+
v3.VChip(
77+
"{{ variables_selected.filter(id => variables_listing.find(v => v.id === id)?.type === vtype.name).length }} {{ vtype.name }}",
78+
v_for="(vtype, idx) in variable_types",
79+
key="idx",
80+
color=("vtype.color",),
81+
v_show=(
82+
"variables_selected.filter(id => variables_listing.find(v => v.id === id)?.type === vtype.name).length",
83+
),
84+
size="small",
85+
closable=True,
86+
click_close=(
87+
"variables_selected = variables_selected.filter(id => variables_listing.find(v => v.id === id)?.type !== vtype.name)",
88+
),
89+
classes="ma-1",
90+
)
8591

8692
v3.VSpacer()
8793
v3.VBtn(

0 commit comments

Comments
 (0)