Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .bumpversion.cfg
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[bumpversion]
current_version = 0.1.16
current_version = 0.1.17
commit = True
tag = True

Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "quickview"
version = "0.1.16"
version = "0.1.17"
description = "An application to explore/analyze data for atmosphere component for E3SM"
authors = [
{name = "Kitware Inc."},
Expand Down
2 changes: 1 addition & 1 deletion quickview/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"""QuickView: Visual Analysis for E3SM Atmosphere Data."""

__version__ = "0.1.16"
__version__ = "0.1.17"
__author__ = "Kitware Inc."
__license__ = "Apache-2.0"
157 changes: 66 additions & 91 deletions quickview/interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@
from quickview.ui.view_settings import ViewProperties, ViewControls
from quickview.ui.toolbar import Toolbar

# Import view management components
# Build color cache here
from quickview.view_manager import build_color_information
from quickview.view_manager import ViewManager
from quickview.utils.state import ViewContext, build_color_information

from paraview.simple import ImportPresets, GetLookupTableNames

Expand Down Expand Up @@ -302,12 +302,12 @@ def update_state_from_source(self):

def update_state_from_config(self, initstate):
source = self.source
self.state.update(initstate)
self.update_available_color_maps()
with self.state as state:
state.surface_vars = source.surface_vars
state.interface_vars = source.interface_vars
state.midpoint_vars = source.midpoint_vars
state.update(initstate)

selection = state.variables
selection_surface = np.isin(state.surface_vars, selection).tolist()
selection_midpoint = np.isin(state.midpoint_vars, selection).tolist()
Expand All @@ -320,52 +320,7 @@ def update_state_from_config(self, initstate):
self.midpoint_vars_state = np.array(selection_midpoint)
self.interface_vars_state = np.array(selection_interface)

# Build registry and populate with saved configuration
self.viewmanager.registry = build_color_information(initstate)

# Sync loaded configuration to contexts
if "variables" in initstate:
for i, var in enumerate(initstate["variables"]):
context = self.viewmanager.registry.get_view(var)
if not context:
context = ViewContext(var, i)
self.viewmanager.registry.register_view(var, context)

# Populate context from loaded state
context.colormap = (
initstate.get("varcolor", [])[i]
if i < len(initstate.get("varcolor", []))
else None
)
context.use_log_scale = (
initstate.get("uselogscale", [])[i]
if i < len(initstate.get("uselogscale", []))
else False
)
context.invert_colors = (
initstate.get("invert", [])[i]
if i < len(initstate.get("invert", []))
else False
)
context.min_value = (
initstate.get("varmin", [])[i]
if i < len(initstate.get("varmin", []))
else None
)
context.max_value = (
initstate.get("varmax", [])[i]
if i < len(initstate.get("varmax", []))
else None
)
context.override_range = (
initstate.get("override_range", [])[i]
if i < len(initstate.get("override_range", []))
else False
)
context.has_been_configured = (
True # Mark as configured since we're loading saved state
)

self.load_variables(use_cached_layout=True)

@trigger("layout_changed")
Expand Down Expand Up @@ -484,7 +439,9 @@ def get_default_colormap(self):

# Fallback to first available colormap
return (
self.state.colormaps[0]["value"] if self.state.colormaps else "Cool to Warm"
self.state.colormaps[0]["value"]
if self.state.colormaps
else "Cool to Warm (Extended)"
)

def load_variables(self, use_cached_layout=False):
Expand Down Expand Up @@ -514,39 +471,48 @@ def load_variables(self, use_cached_layout=False):
vars = surf + mid + intf

# Tracking variables to control camera and color properties
state = self.state
state.variables = vars

# Initialize arrays with proper size
state.varcolor = [""] * len(vars)
state.uselogscale = [False] * len(vars)
state.invert = [False] * len(vars)
state.varmin = [0] * len(vars)
state.varmax = [1] * len(vars)
state.override_range = [False] * len(vars)
state.colorbar_images = [""] * len(vars) # Initialize empty images
state.varaverage = [0] * len(vars)

# Check if variables already have contexts (still selected, just updating)
# Preserve configuration for variables that remain selected
for i, var in enumerate(vars):
context = self.viewmanager.registry.get_view(var)
if context and context.has_been_configured:
# Variable is still selected, preserve its configuration
state.varcolor[i] = context.colormap or self.get_default_colormap()
state.uselogscale[i] = context.use_log_scale
state.invert[i] = context.invert_colors
state.varmin[i] = (
context.min_value if context.min_value is not None else 0
)
state.varmax[i] = (
context.max_value if context.max_value is not None else 1
)
state.override_range[i] = context.override_range
with self.state as state:
state.variables = vars

# When loading from cached state, preserve existing color values
# Otherwise, initialize with defaults
if not use_cached_layout:
state.varcolor = [self.get_default_colormap()] * len(vars)
state.uselogscale = [False] * len(vars)
state.invert = [False] * len(vars)
state.varmin = [np.nan] * len(vars)
state.varmax = [np.nan] * len(vars)
state.override_range = [False] * len(vars)
state.colorbar_images = [""] * len(vars) # Initialize empty images
state.varaverage = [np.nan] * len(vars)
else:
# New variable or was deselected, use defaults
state.varcolor[i] = self.get_default_colormap()
# Other values remain as initialized defaults
# Preserve loaded values but ensure arrays match variable count
# Extend or trim arrays to match new variable count if needed
current_len = (
len(state.varcolor)
if hasattr(state, "varcolor") and state.varcolor
else 0
)
if current_len != len(vars):
# If array lengths don't match, extend with defaults or trim
default_colormap = self.get_default_colormap()
state.varcolor = (state.varcolor + [default_colormap] * len(vars))[
: len(vars)
]
state.uselogscale = (state.uselogscale + [False] * len(vars))[
: len(vars)
]
state.invert = (state.invert + [False] * len(vars))[: len(vars)]
state.varmin = (state.varmin + [np.nan] * len(vars))[: len(vars)]
state.varmax = (state.varmax + [np.nan] * len(vars))[: len(vars)]
state.override_range = (state.override_range + [False] * len(vars))[
: len(vars)
]
state.varaverage = (state.varaverage + [np.nan] * len(vars))[
: len(vars)
]
# Always reset colorbar images as they need to be regenerated
state.colorbar_images = [""] * len(vars)

# Only use cached layout when explicitly requested (i.e., when loading state)
layout_to_use = self._cached_layout if use_cached_layout else None
Expand Down Expand Up @@ -713,19 +679,28 @@ def search_interface_vars(self, search: str):
].tolist()
self.state.dirty("interface_vars_state")

def clear_surface_vars(self):
self.state.surface_vars_state = [False] * len(self.state.surface_vars_state)
self.surface_vars_state = np.array([False] * len(self.surface_vars_state))
def clear_surface_vars(self, clear_var_name):
self.state[clear_var_name] = ""
self.ind_surface = None
self.state.surface_vars = self.source.surface_vars
self.state.surface_vars_state = [False] * len(self.source.surface_vars)
self.surface_vars_state = np.array([False] * len(self.source.surface_vars))
self.state.dirty("surface_vars_state")

def clear_midpoint_vars(self):
self.state.midpoint_vars_state = [False] * len(self.state.midpoint_vars_state)
self.midpoint_vars_state = np.array([False] * len(self.midpoint_vars_state))
def clear_midpoint_vars(self, clear_var_name):
self.state[clear_var_name] = ""
self.ind_midpoint = None
self.state.midpoint_vars = self.source.midpoint_vars
self.state.midpoint_vars_state = [False] * len(self.source.midpoint_vars)
self.midpoint_vars_state = np.array([False] * len(self.source.midpoint_vars))
self.state.dirty("midpoint_vars_state")

def clear_interface_vars(self):
self.state.interface_vars_state = [False] * len(self.state.interface_vars_state)
self.interface_vars_state = np.array([False] * len(self.interface_vars_state))
def clear_interface_vars(self, clear_var_name):
self.state[clear_var_name] = ""
self.ind_interface = None
self.state.interface_vars = self.source.interface_vars
self.state.interface_vars_state = [False] * len(self.source.interface_vars)
self.interface_vars_state = np.array([False] * len(self.source.interface_vars))
self.state.dirty("interface_vars_state")

def start(self, **kwargs):
Expand Down
1 change: 0 additions & 1 deletion quickview/pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,6 @@ def Update(self, data_file, conn_file, midpoint=0, interface=0, force_reload=Fal
self.timestamps = (
[timestep_values] if timestep_values is not None else []
)
print(self.timestamps)

# Step 1: Extract and transform atmospheric data
atmos_extract = EAMTransformAndExtract(
Expand Down
2 changes: 1 addition & 1 deletion quickview/plugins/eam_filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,7 @@ def __init__(self):
self.__Dims = -1

def SetZonalAverage(self, zonal):
print("Checked zonal : ", zonal)
pass

def RequestData(self, request, inInfo, outInfo):
global _has_deps
Expand Down
2 changes: 1 addition & 1 deletion quickview/ui/projection_selection.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,6 @@ def update_pipeline_interactive(self, **kwargs):
time = 0.0 if len(self.state.timesteps) == 0 else self.state.timesteps[tstamp]
self.source.UpdatePipeline(time)
# For projection changes, we need to fit viewports to new bounds
self.views.update_views_for_timestep(fit_viewport=True)
self.views.update_views_for_timestep()
# Render once after all updates
self.views.render_all_views()
38 changes: 14 additions & 24 deletions quickview/ui/slice_selection.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,27 +169,22 @@ def __init__(self, source: EAMVisSource, view_manager: ViewManager):

with v2.VRow(classes="text-center align-center text-subtitle-1 pt-2 pa-2"):
with v2.VCol(cols=3, classes="py-0"):
v2.VTextField(
v_model=("cliplong[0]",),
label="Min",
suffix="°",
classes="py-0",
**style,
html.Div(
"{{ cliplong[0].toFixed(1) }}°",
classes="font-weight-medium text-center",
)
with v2.VCol(cols=6, classes="py-0"):
html.Div("Longitude")
with v2.VCol(cols=3, classes="py-0"):
v2.VTextField(
v_model=("cliplong[1]",),
label="Max",
suffix="°",
classes="py-0",
**style,
html.Div(
"{{ cliplong[1].toFixed(1) }}°",
classes="font-weight-medium text-center",
)
v2.VRangeSlider(
v_model=("cliplong", [self.source.extents[0], self.source.extents[1]]),
min=-180,
max=180,
step=0.5,
color="blue-grey",
**style,
flat=True,
Expand All @@ -200,27 +195,22 @@ def __init__(self, source: EAMVisSource, view_manager: ViewManager):

with v2.VRow(classes="text-center align-center text-subtitle-1 pt-4 px-2"):
with v2.VCol(cols=3, classes="py-0"):
v2.VTextField(
v_model=("cliplat[0]",),
label="Min",
suffix="°",
classes="py-0",
**style,
html.Div(
"{{ cliplat[0].toFixed(1) }}°",
classes="font-weight-medium text-center",
)
with v2.VCol(cols=6, classes="py-0"):
html.Div("Latitude")
with v2.VCol(cols=3, classes="py-0"):
v2.VTextField(
v_model=("cliplat[1]",),
label="Max",
suffix="°",
classes="py-0",
**style,
html.Div(
"{{ cliplat[1].toFixed(1) }}°",
classes="font-weight-medium text-center",
)
v2.VRangeSlider(
v_model=("cliplat", [self.source.extents[2], self.source.extents[3]]),
min=-90,
max=90,
step=0.5,
color="blue-grey",
**style,
flat=True,
Expand Down
16 changes: 14 additions & 2 deletions quickview/ui/variable_selection.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,14 @@ def __init__(self, variables, state, update=None):

@TrameApp()
class VariableSelection(CollapsableSection):
_next_id = 0

@classmethod
def next_id(cls):
"""Get the next unique ID for the scalar bar."""
cls._next_id += 1
return f"var_select_{cls._next_id}"

def __init__(
self,
title=None,
Expand All @@ -48,13 +56,17 @@ def __init__(
on_update=None,
):
super().__init__(title=title, var_name=panel_name)

ns = self.next_id()
self.__search_var = f"{ns}_search"

with self.content:
# Search and controls section
with v2.VCard(flat=True, elevation=0, classes="pa-2 mb-1"):
with v2.VRow(classes="align-center", no_gutters=True):
with v2.VCol(cols=9, classes="pr-1"):
v2.VTextField(
v_model=("variableSearchQuery", ""),
v_model=(self.__search_var, ""),
prepend_inner_icon="mdi-magnify",
label="Search variables",
placeholder="Type to filter...",
Expand All @@ -69,7 +81,7 @@ def __init__(
with v2.VTooltip(bottom=True):
with html.Template(v_slot_activator="{ on, attrs }"):
with v2.VBtn(
click=(on_clear),
click=(on_clear, f"['{self.__search_var}']"),
depressed=True,
small=True,
v_bind="attrs",
Expand Down
Loading
Loading