diff --git a/.bumpversion.cfg b/.bumpversion.cfg index c8db3f3..b5993b3 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 0.1.1 +current_version = 0.1.2 commit = True tag = True diff --git a/.github/workflows/package-and-release.yml b/.github/workflows/package-and-release.yml index 2269c09..e8a1b95 100644 --- a/.github/workflows/package-and-release.yml +++ b/.github/workflows/package-and-release.yml @@ -88,12 +88,3 @@ jobs: releaseDraft: true prerelease: false args: ${{ matrix.args }} - - - name: Create git tag - if: - matrix.platform == 'macos-latest' && matrix.args == '--target - aarch64-apple-darwin' - run: | - git tag v${{ env.VERSION }} - git push origin v${{ env.VERSION }} - shell: bash -l {0} diff --git a/pyproject.toml b/pyproject.toml index ebe6105..8f4ec09 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "quickview" -version = "0.1.1" +version = "0.1.2" description = "An application to explore/analyze data for atmosphere component for E3SM" authors = [ {name = "Kitware Inc."}, diff --git a/quickview/__init__.py b/quickview/__init__.py index 0d46f69..ef9dc08 100644 --- a/quickview/__init__.py +++ b/quickview/__init__.py @@ -1,5 +1,5 @@ """QuickView: Visual Analysis for E3SM Atmosphere Data.""" -__version__ = "0.1.1" +__version__ = "0.1.2" __author__ = "Kitware Inc." __license__ = "Apache-2.0" diff --git a/quickview/interface.py b/quickview/interface.py index 2e7c27d..d134cad 100644 --- a/quickview/interface.py +++ b/quickview/interface.py @@ -7,7 +7,7 @@ from typing import Union from trame.app import get_server -from trame.decorators import TrameApp, life_cycle +from trame.decorators import TrameApp, life_cycle, trigger from trame.ui.vuetify import SinglePageWithDrawerLayout from trame.widgets import vuetify as v2, html, client @@ -84,6 +84,8 @@ "use_cvd_colors", "use_standard_colors", "show_color_bar", + # Grid layout + "layout", ] @@ -117,6 +119,7 @@ def __init__( ctrl = server.controller self._ui = None + self._cached_layout = {} # Cache for layout positions by variable name self.workdir = workdir self.server = server @@ -269,16 +272,73 @@ def update_state_from_config(self, initstate): self.viewmanager.registry = build_color_information(initstate) self.load_variables() + @trigger("layout_changed") + def on_layout_changed_trigger(self, layout, **kwargs): + """Cache layout changes to ensure they are properly saved""" + # Cache the layout data with variable names as keys for easier lookup + self._cached_layout = {} + if layout and hasattr(self.state, "variables"): + for item in layout: + if isinstance(item, dict) and "i" in item: + idx = item["i"] + if idx < len(self.state.variables): + var_name = self.state.variables[idx] + self._cached_layout[var_name] = { + "x": item.get("x", 0), + "y": item.get("y", 0), + "w": item.get("w", 4), + "h": item.get("h", 3), + } + def generate_state(self): + # Force state synchronization + self.state.flush() + all = self.state.to_dict() to_export = {k: all[k] for k in save_state_keys} - # with open(os.path.join(self.workdir, "state.json"), "w") as outfile: + + # Convert cached layout back to array format for saving + if self._cached_layout and hasattr(self.state, "variables"): + layout_array = [] + for idx, var_name in enumerate(self.state.variables): + if var_name in self._cached_layout: + pos = self._cached_layout[var_name] + layout_array.append( + { + "x": pos["x"], + "y": pos["y"], + "w": pos["w"], + "h": pos["h"], + "i": idx, + } + ) + if layout_array: + to_export["layout"] = layout_array + return to_export def load_state(self, state_file): from_state = json.loads(Path(state_file).read_text()) data_file = from_state["data_file"] conn_file = from_state["conn_file"] + # Convert loaded layout to variable-name-based cache + self._cached_layout = {} + if ( + "layout" in from_state + and from_state["layout"] + and "variables" in from_state + ): + for item in from_state["layout"]: + if isinstance(item, dict) and "i" in item: + idx = item["i"] + if idx < len(from_state["variables"]): + var_name = from_state["variables"][idx] + self._cached_layout[var_name] = { + "x": item.get("x", 0), + "y": item.get("y", 0), + "w": item.get("w", 4), + "h": item.get("h", 3), + } self.source.Update( data_file=data_file, conn_file=conn_file, @@ -336,7 +396,21 @@ def load_variables(self): state.varmax = [np.nan] * len(vars) state.override_range = [False] * len(vars) - self.viewmanager.rebuild_visualization_layout() + self.viewmanager.rebuild_visualization_layout(self._cached_layout) + # Update cached layout after rebuild + if state.layout and state.variables: + self._cached_layout = {} + for item in state.layout: + if isinstance(item, dict) and "i" in item: + idx = item["i"] + if idx < len(state.variables): + var_name = state.variables[idx] + self._cached_layout[var_name] = { + "x": item.get("x", 0), + "y": item.get("y", 0), + "w": item.get("w", 4), + "h": item.get("h", 3), + } def update_view_color_settings(self, index, type, value): with self.state as state: @@ -594,7 +668,13 @@ def ui(self) -> SinglePageWithDrawerLayout: with layout.content: with grid.GridLayout( - layout=("layout", []), + layout=("layout"), + col_num=12, + row_height=100, + is_draggable=True, + is_resizable=True, + vertical_compact=True, + layout_updated="layout = $event; trigger('layout_changed', [$event])", ): with grid.GridItem( v_for="vref, idx in views", diff --git a/quickview/ui/toolbar.py b/quickview/ui/toolbar.py index 2e603c0..e5173d5 100644 --- a/quickview/ui/toolbar.py +++ b/quickview/ui/toolbar.py @@ -20,6 +20,11 @@ async def select_connectivity_file(self): @task async def export_state(self): + # Small delay to ensure client state is synchronized + import asyncio + + await asyncio.sleep(0.1) + if self._generate_state is not None: config = self._generate_state() with self.state: @@ -109,6 +114,7 @@ def __init__( with layout_toolbar as toolbar: toolbar.density = "compact" + toolbar.style = "overflow-x: auto; overflow-y: hidden;" v2.VDivider(vertical=True, classes="mx-2") v2.VBtn( "Load Variables", @@ -126,7 +132,7 @@ def __init__( with v2.VCard( flat=True, classes="d-flex align-center px-2 py-1 mx-1", - style="background-color: #f5f5f5; border-radius: 4px;", + style="background-color: #f5f5f5; border-radius: 4px; flex-shrink: 0;", ): with v2.VTooltip(bottom=True): with html.Template(v_slot_activator="{ on, attrs }"): @@ -175,7 +181,7 @@ def __init__( with v2.VCard( flat=True, classes="d-flex align-center px-2 py-1 mx-1", - style="background-color: #f5f5f5; border-radius: 4px; min-width: 35%;", + style="background-color: #f5f5f5; border-radius: 4px; min-width: 35%; flex-shrink: 1;", ): with v2.VTooltip(bottom=True): with html.Template(v_slot_activator="{ on, attrs }"): @@ -268,7 +274,7 @@ def __init__( with v2.VCard( flat=True, classes="d-flex align-center px-2 py-1 mx-1", - style="background-color: #f5f5f5; border-radius: 4px;", + style="background-color: #f5f5f5; border-radius: 4px; flex-shrink: 0;", ): with v2.VTooltip(bottom=True): with html.Template(v_slot_activator="{ on, attrs }"): diff --git a/quickview/view_manager.py b/quickview/view_manager.py index 3eb9b87..ec5d15e 100644 --- a/quickview/view_manager.py +++ b/quickview/view_manager.py @@ -193,6 +193,8 @@ def build_color_information(state: map): varmax = state["varmax"] # Get override_range from state if available override_range = state.get("override_range", None) + # Store layout from state if available for backward compatibility + layout = state.get("layout", None) registry = ViewRegistry() for index, var in enumerate(vars): @@ -215,6 +217,11 @@ def build_color_information(state: map): view_state = ViewState() context = ViewContext(config, view_state, index) registry.register_view(var, context) + + # Store layout info in registry for later use + if layout: + registry._saved_layout = [item.copy() for item in layout] + return registry @@ -408,7 +415,7 @@ def compute_range(self, var, vtkdata=None): vardata = vtkdata.GetCellData().GetArray(var) return vardata.GetRange() - def rebuild_visualization_layout(self): + def rebuild_visualization_layout(self, cached_layout=None): self.widgets.clear() state = self.state source = self.source @@ -434,6 +441,26 @@ def rebuild_visualization_layout(self): data = self.source.views["atmosphere_data"] vtkdata = sm.Fetch(data) + # Use cached layout if provided, or fall back to saved layout in registry + layout_map = cached_layout if cached_layout else {} + + # If no cached layout, check if we have saved layout in registry + if not layout_map and hasattr(self.registry, "_saved_layout"): + # Convert saved layout array to variable-name-based map + temp_map = {} + for item in self.registry._saved_layout: + if isinstance(item, dict) and "i" in item: + idx = item["i"] + if hasattr(state, "variables") and idx < len(state.variables): + var_name = state.variables[idx] + temp_map[var_name] = { + "x": item.get("x", 0), + "y": item.get("y", 0), + "w": item.get("w", 4), + "h": item.get("h", 3), + } + layout_map = temp_map + del self.state.views[:] del self.state.layout[:] del self.widgets[:] @@ -444,8 +471,20 @@ def rebuild_visualization_layout(self): view0 = None for index, var in enumerate(to_render): - x = int(index % 3) * wdt - y = int(index / 3) * hgt + # Check if we have saved position for this variable + if var in layout_map: + # Use saved position + pos = layout_map[var] + x = pos["x"] + y = pos["y"] + wdt = pos["w"] + hgt = pos["h"] + else: + # Default grid position (3 columns) + x = int(index % 3) * 4 + y = int(index / 3) * 3 + wdt = 4 + hgt = 3 varrange = self.compute_range(var, vtkdata=vtkdata) varavg = self.compute_average(var, vtkdata=vtkdata) @@ -510,6 +549,7 @@ def rebuild_visualization_layout(self): ) self.widgets.append(widget) sWidgets.append(widget.ref_name) + # Use index as identifier to maintain compatibility with grid expectations layout.append({"x": x, "y": y, "w": wdt, "h": hgt, "i": index}) for var in to_delete: @@ -518,6 +558,7 @@ def rebuild_visualization_layout(self): self.state.views = sWidgets self.state.layout = layout self.state.dirty("views") + self.state.dirty("layout") # from trame.app import asynchronous # asynchronous.create_task(self.flushViews()) diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index d292009..c68d7b9 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "app" -version = "0.1.1" +version = "0.1.2" description = "QuickView: Visual Analyis for E3SM Atmosphere Data" authors = ["Kitware"] license = "" diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index f6e7ce3..3c389fb 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -7,7 +7,7 @@ }, "package": { "productName": "QuickView", - "version": "0.1.1" + "version": "0.1.2" }, "tauri": { "allowlist": {