diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 9d3d2f0..de659a9 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 0.1.3 +current_version = 0.1.5 commit = True tag = True diff --git a/pyproject.toml b/pyproject.toml index aaa96f1..621a87d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "quickview" -version = "0.1.3" +version = "0.1.5" 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 7b6d16e..957169c 100644 --- a/quickview/__init__.py +++ b/quickview/__init__.py @@ -1,5 +1,5 @@ """QuickView: Visual Analysis for E3SM Atmosphere Data.""" -__version__ = "0.1.3" +__version__ = "0.1.5" __author__ = "Kitware Inc." __license__ = "Apache-2.0" diff --git a/quickview/interface.py b/quickview/interface.py index 5c000f7..60903ea 100644 --- a/quickview/interface.py +++ b/quickview/interface.py @@ -26,9 +26,6 @@ # Build color cache here from quickview.view_manager import build_color_information - -from quickview.utilities import EventType - from quickview.view_manager import ViewManager from paraview.simple import ImportPresets, GetLookupTableNames @@ -83,7 +80,6 @@ # Color options from toolbar "use_cvd_colors", "use_standard_colors", - "show_color_bar", # Grid layout "layout", ] @@ -189,6 +185,7 @@ def __init__( state.varmin = [] state.varmax = [] state.override_range = [] + state.colorbar_images = [] ctrl.view_update = self.viewmanager.render_all_views ctrl.view_reset_camera = self.viewmanager.reset_camera @@ -401,6 +398,7 @@ def load_variables(self): 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 self.viewmanager.rebuild_visualization_layout(self._cached_layout) # Update cached layout after rebuild @@ -418,18 +416,17 @@ def load_variables(self): "h": item.get("h", 3), } - def update_view_color_settings(self, index, type, value): - with self.state as state: - if type == EventType.COL.value: - state.varcolor[index] = value - state.dirty("varcolor") - elif type == EventType.LOG.value: - state.uselogscale[index] = value - state.dirty("uselogscale") - elif type == EventType.INV.value: - state.invert[index] = value - state.dirty("invert") - self.viewmanager.update_view_color_settings(index, type, value) + def update_colormap(self, index, value): + """Update the colormap for a variable.""" + self.viewmanager.update_colormap(index, value) + + def update_log_scale(self, index, value): + """Update the log scale setting for a variable.""" + self.viewmanager.update_log_scale(index, value) + + def update_invert_colors(self, index, value): + """Update the color inversion setting for a variable.""" + self.viewmanager.update_invert_colors(index, value) def update_scalar_bars(self, event): self.viewmanager.update_scalar_bars(event) @@ -448,16 +445,11 @@ def update_available_color_maps(self): state.colormaps = noncvd def set_manual_color_range(self, index, type, value): - with self.state as state: - if type.lower() == "min": - state.varmin[index] = value - state.dirty("varmin") - elif type.lower() == "max": - state.varmax[index] = value - state.dirty("varmax") - self.viewmanager.set_manual_color_range( - index, state.varmin[index], state.varmax[index] - ) + # Get current values from state to handle min/max independently + min_val = self.state.varmin[index] if type.lower() == "max" else value + max_val = self.state.varmax[index] if type.lower() == "min" else value + # Delegate to view manager which will update both the view and sync state + self.viewmanager.set_manual_color_range(index, min_val, max_val) def revert_to_auto_color_range(self, index): self.viewmanager.revert_to_auto_color_range(index) @@ -606,7 +598,6 @@ def ui(self) -> SinglePageWithDrawerLayout: load_state=self.load_state, load_variables=self.load_variables, update_available_color_maps=self.update_available_color_maps, - update_scalar_bars=self.update_scalar_bars, generate_state=self.generate_state, ) @@ -695,9 +686,10 @@ def ui(self) -> SinglePageWithDrawerLayout: style="height: calc(100% - 0.66rem); position: relative;", classes="pa-0", ) as cardcontent: + # VTK View takes up most of the space cardcontent.add_child( """ - + """, ) @@ -719,17 +711,50 @@ def ui(self) -> SinglePageWithDrawerLayout: # height: $refs[vref].vtkContainer.getBoundingClientRect().height}] # ''') ) + # Mask to prevent VTK view from getting scroll/mouse events html.Div( - style="position:absolute; top: 0; left: 0; width: 100%; height: calc(100% - 0.66rem); z-index: 1;" + style="position:absolute; top: 0; left: 0; width: 100%; height: calc(100% - 30px); z-index: 1;" ) - # with v2.VCardActions(classes="pa-0"): + # View Properties with html.Div( - style="position:absolute; bottom: 1rem; left: 1rem; height: 2rem; z-index: 2;" + style="position:absolute; bottom: 40px; left: 1rem; height: 2rem; z-index: 2;" ): ViewProperties( - apply=self.update_view_color_settings, - update=self.set_manual_color_range, + update_colormap=self.update_colormap, + update_log_scale=self.update_log_scale, + update_invert=self.update_invert_colors, + update_range=self.set_manual_color_range, reset=self.revert_to_auto_color_range, ) + # Colorbar container (horizontal layout at bottom) + with html.Div( + style="position: absolute; bottom: 0; left: 0; right: 0; display: flex; align-items: center; padding: 4px 12px; background-color: rgba(255, 255, 255, 0.9); height: 30px; z-index: 3; overflow: visible;", + classes="drag-ignore", + ): + # Color min value + html.Span( + "{{ varmin[idx] !== null && !isNaN(varmin[idx]) ? (uselogscale[idx] && varmin[idx] > 0 ? 'log₁₀(' + Math.log10(varmin[idx]).toFixed(3) + ')' : varmin[idx].toFixed(3)) : 'Auto' }}", + style="font-size: 12px; color: #333; white-space: nowrap;", + ) + # Colorbar + with html.Div( + style="flex: 1; position: relative; margin: 0 12px; height: 0.75rem;", + classes="drag-ignore", + ): + # Colorbar image + html.Img( + src=( + "colorbar_images[idx] || 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg=='", + None, + ), + style="height: 100%; width: 100%; object-fit: fill;", + classes="rounded-lg border-thin", + ) + # Color max value + html.Span( + "{{ varmax[idx] !== null && !isNaN(varmax[idx]) ? (uselogscale[idx] && varmax[idx] > 0 ? 'log₁₀(' + Math.log10(varmax[idx]).toFixed(3) + ')' : varmax[idx].toFixed(3)) : 'Auto' }}", + style="font-size: 12px; color: #333; white-space: nowrap;", + ) + return self._ui diff --git a/quickview/ui/toolbar.py b/quickview/ui/toolbar.py index e5173d5..094d49c 100644 --- a/quickview/ui/toolbar.py +++ b/quickview/ui/toolbar.py @@ -1,5 +1,11 @@ from trame.decorators import TrameApp, task -from trame.widgets import html, vuetify2 as v2, tauri +from trame.widgets import html, vuetify2 as v2 + +try: + from trame.widgets import tauri +except ImportError: + # Fallback if tauri is not available + tauri = None import json @@ -71,13 +77,6 @@ def _update_color_maps(self): # Directly call update_available_color_maps without parameters self._update_available_color_maps() - def _handle_color_bar_toggle(self): - """Toggle the color bar visibility""" - with self.state: - self.state.show_color_bar = not self.state.show_color_bar - if self._update_scalar_bars is not None: - self._update_scalar_bars(self.state.show_color_bar) - def __init__( self, layout_toolbar, @@ -86,32 +85,30 @@ def __init__( load_state=None, load_variables=None, update_available_color_maps=None, - update_scalar_bars=None, generate_state=None, **kwargs, ): self.server = server - with tauri.Dialog() as dialog: - self.ctrl.open = dialog.open - self.ctrl.save = dialog.save + if tauri: + with tauri.Dialog() as dialog: + self.ctrl.open = dialog.open + self.ctrl.save = dialog.save + else: + # Fallback for non-tauri environments + self.ctrl.open = lambda title: None + self.ctrl.save = lambda title: None self._generate_state = generate_state self._load_state = load_state self._update_available_color_maps = update_available_color_maps - self._update_scalar_bars = update_scalar_bars # Initialize toggle states with self.state: self.state.use_cvd_colors = False self.state.use_standard_colors = True - self.state.show_color_bar = True # Set initial color maps based on default toggle states self._update_color_maps() - # Apply initial scalar bar visibility - if self._update_scalar_bars is not None: - self._update_scalar_bars(True) - with layout_toolbar as toolbar: toolbar.density = "compact" toolbar.style = "overflow-x: auto; overflow-y: hidden;" @@ -162,21 +159,6 @@ def __init__( ): v2.VIcon("mdi-palette") html.Span("Standard colors") - v2.VDivider(vertical=True, classes="mx-2", style="height: 24px;") - with v2.VTooltip(bottom=True): - with html.Template(v_slot_activator="{ on, attrs }"): - with v2.VBtn( - icon=True, - dense=True, - small=True, - v_bind="attrs", - v_on="on", - click=self._handle_color_bar_toggle, - color=("show_color_bar ? 'primary' : ''",), - classes="mx-1", - ): - v2.VIcon("mdi-format-color-fill") - html.Span("Show color bar") v2.VDivider(vertical=True, classes="mx-2") with v2.VCard( flat=True, diff --git a/quickview/ui/view_settings.py b/quickview/ui/view_settings.py index 52ec981..12ab93b 100644 --- a/quickview/ui/view_settings.py +++ b/quickview/ui/view_settings.py @@ -1,12 +1,18 @@ from trame.widgets import vuetify2 as v2, html from trame.decorators import TrameApp -from quickview.utilities import EventType - @TrameApp() class ViewProperties(v2.VMenu): - def __init__(self, apply=None, update=None, reset=None, **kwargs): + def __init__( + self, + update_colormap=None, + update_log_scale=None, + update_invert=None, + update_range=None, + reset=None, + **kwargs, + ): super().__init__( transition="slide-y-transition", close_on_content_click=False, @@ -37,8 +43,8 @@ def __init__(self, apply=None, update=None, reset=None, **kwargs): items=("colormaps",), outlined=True, change=( - apply, - f"[idx, {EventType.COL.value}, $event]", + update_colormap, + "[idx, $event]", ), **style, ) @@ -49,8 +55,8 @@ def __init__(self, apply=None, update=None, reset=None, **kwargs): label="Log Scale", v_model=("uselogscale[idx]",), change=( - apply, - f"[idx, {EventType.LOG.value}, $event]", + update_log_scale, + "[idx, $event]", ), **style, ) @@ -59,8 +65,8 @@ def __init__(self, apply=None, update=None, reset=None, **kwargs): label="Revert Colors", v_model=("invert[idx]",), change=( - apply, - f"[idx, {EventType.INV.value}, $event]", + update_invert, + "[idx, $event]", ), **style, ) @@ -82,7 +88,7 @@ def __init__(self, apply=None, update=None, reset=None, **kwargs): label="min", outlined=True, change=( - update, + update_range, "[idx, 'min', $event]", ), style="height=50px", @@ -95,7 +101,7 @@ def __init__(self, apply=None, update=None, reset=None, **kwargs): label="max", outlined=True, change=( - update, + update_range, "[idx, 'max', $event]", ), style="height=50px", diff --git a/quickview/utilities.py b/quickview/utilities.py index 473a166..f5914f5 100644 --- a/quickview/utilities.py +++ b/quickview/utilities.py @@ -1,11 +1,10 @@ import os -from enum import Enum +import base64 +import numpy as np - -class EventType(Enum): - COL = 0 - LOG = 1 - INV = 2 +from vtkmodules.vtkCommonCore import vtkUnsignedCharArray, vtkLookupTable +from vtkmodules.vtkCommonDataModel import vtkImageData +from vtkmodules.vtkIOImage import vtkPNGWriter def ValidateArguments(conn_file, data_file, state_file, work_dir): @@ -24,3 +23,136 @@ def ValidateArguments(conn_file, data_file, state_file, work_dir): if work_dir is None: print("No working directory is provided, using current directory as default") return True + + +def get_lut_from_color_transfer_function(paraview_lut, num_colors=256): + """ + Convert a ParaView color transfer function to a VTK lookup table. + + Parameters: + ----------- + paraview_lut : paraview.servermanager.PVLookupTable + The ParaView color transfer function from GetColorTransferFunction() + num_colors : int, optional + Number of colors in the VTK lookup table (default: 256) + + Returns: + -------- + vtkLookupTable + A VTK lookup table with interpolated colors from the ParaView LUT + """ + # Get RGB points from ParaView LUT + rgb_points = paraview_lut.RGBPoints + + if len(rgb_points) < 8: + raise ValueError("ParaView LUT must have at least 2 color points") + + # Create VTK lookup table + vtk_lut = vtkLookupTable() + + # Extract scalars and colors from the flat RGB points array + scalars = np.array([rgb_points[i] for i in range(0, len(rgb_points), 4)]) + colors = np.array( + [ + [rgb_points[i + 1], rgb_points[i + 2], rgb_points[i + 3]] + for i in range(0, len(rgb_points), 4) + ] + ) + + # Get range + min_val = scalars[0] + max_val = scalars[-1] + + # Generate all scalar values for the lookup table + table_scalars = np.linspace(min_val, max_val, num_colors) + + # Vectorized interpolation for all colors at once + r_values = np.interp(table_scalars, scalars, colors[:, 0]) + g_values = np.interp(table_scalars, scalars, colors[:, 1]) + b_values = np.interp(table_scalars, scalars, colors[:, 2]) + + # Set up the VTK lookup table + vtk_lut.SetRange(min_val, max_val) + vtk_lut.SetNumberOfTableValues(num_colors) + vtk_lut.Build() + + # Set all colors at once + for i in range(num_colors): + vtk_lut.SetTableValue(i, r_values[i], g_values[i], b_values[i], 1.0) + + return vtk_lut + + +def vtk_lut_to_image(lut, samples=255): + """ + Convert a VTK lookup table to a base64-encoded PNG image. + + Parameters: + ----------- + lut : vtkLookupTable + The VTK lookup table to convert + samples : int, optional + Number of samples for the color bar (default: 255) + + Returns: + -------- + str + Base64-encoded PNG image as a data URI + """ + colorArray = vtkUnsignedCharArray() + colorArray.SetNumberOfComponents(3) + colorArray.SetNumberOfTuples(samples) + + dataRange = lut.GetRange() + delta = (dataRange[1] - dataRange[0]) / float(samples) + + # Add the color array to an image data + imgData = vtkImageData() + imgData.SetDimensions(samples, 1, 1) + imgData.GetPointData().SetScalars(colorArray) + + # Loop over all presets + rgb = [0, 0, 0] + for i in range(samples): + lut.GetColor(dataRange[0] + float(i) * delta, rgb) + r = int(round(rgb[0] * 255)) + g = int(round(rgb[1] * 255)) + b = int(round(rgb[2] * 255)) + colorArray.SetTuple3(i, r, g, b) + + writer = vtkPNGWriter() + writer.WriteToMemoryOn() + writer.SetInputData(imgData) + writer.SetCompressionLevel(6) + writer.Write() + + writer.GetResult() + + base64_img = base64.standard_b64encode(writer.GetResult()).decode("utf-8") + return f"data:image/png;base64,{base64_img}" + + +def build_colorbar_image(paraview_lut, log_scale=False, invert=False): + """ + Build a colorbar image from a ParaView color transfer function. + + Parameters: + ----------- + paraview_lut : paraview.servermanager.PVLookupTable + The ParaView color transfer function + log_scale : bool, optional + Whether to apply log scale (affects data mapping, not image) + invert : bool, optional + Whether to invert colors (will affect the image) + + Returns: + -------- + str + Base64-encoded PNG image as a data URI + """ + # Convert to VTK LUT - this will get the current state from ParaView + # including any inversions already applied by InvertTransferFunction + vtk_lut = get_lut_from_color_transfer_function(paraview_lut) + + # Convert to image + return vtk_lut_to_image(vtk_lut) diff --git a/quickview/view_manager.py b/quickview/view_manager.py index ec5d15e..3adb952 100644 --- a/quickview/view_manager.py +++ b/quickview/view_manager.py @@ -11,7 +11,6 @@ Text, Show, CreateRenderView, - GetScalarBar, ColorBy, GetColorTransferFunction, AddCameraLink, @@ -19,7 +18,7 @@ ) from quickview.pipeline import EAMVisSource -from quickview.utilities import EventType +from quickview.utilities import build_colorbar_image from typing import Dict, List, Optional @@ -258,6 +257,7 @@ def update_views_for_timestep(self): if context.state.var_info_proxy is not None: context.state.var_info_proxy.Text = V_info self.sync_color_config_to_state(context.index, context) + self.generate_colorbar_image(context.index) def refresh_view_display(self, var, context: ViewContext): if not context.config.override_range: @@ -303,13 +303,12 @@ def configure_new_view(self, var, context: ViewContext, sources): coltrfunc = GetColorTransferFunction(var) coltrfunc.ApplyPreset(context.config.colormap, True) coltrfunc.NanOpacity = 0.0 - LUTColorBar = GetScalarBar(coltrfunc, rview) - LUTColorBar.AutoOrient = 1 - LUTColorBar.WindowLocation = "Lower Right Corner" - LUTColorBar.Title = "" - LUTColorBar.ComponentTitle = "" - LUTColorBar.ScalarBarLength = 0.75 - # LUTColorBar.NanOpacity = 0.0 + + # Ensure the color transfer function is scaled to the data range + if not context.config.override_range: + rep.RescaleTransferFunctionToDataRange(False, True) + + # ParaView scalar bar is always hidden - using custom HTML colorbar instead (v_text, V_info) = self.get_var_info(var, context.state.computed_average) text = Text(registrationName=f"Text{var}") @@ -344,19 +343,73 @@ def configure_new_view(self, var, context: ViewContext, sources): repAn.DiffuseColor = [0.67, 0.67, 0.67] repAn.Opacity = 0.4 - rep.SetScalarBarVisibility(rview, self.state.show_color_bar) + # Always hide ParaView scalar bar - using custom HTML colorbar + rep.SetScalarBarVisibility(rview, False) rview.CameraParallelProjection = 1 Render(rview) # ResetCamera(rview) def sync_color_config_to_state(self, index, context: ViewContext): - with self.state as state: - state.varcolor[index] = context.config.colormap - state.varmin[index] = context.config.min_value - state.varmax[index] = context.config.max_value - state.uselogscale[index] = context.config.use_log_scale - state.override_range[index] = context.config.override_range + # Update state arrays directly without context manager to avoid recursive flush + self.state.varcolor[index] = context.config.colormap + self.state.varmin[index] = context.config.min_value + self.state.varmax[index] = context.config.max_value + self.state.uselogscale[index] = context.config.use_log_scale + self.state.override_range[index] = context.config.override_range + # Mark arrays as dirty to ensure UI updates + self.state.dirty("varcolor") + self.state.dirty("varmin") + self.state.dirty("varmax") + self.state.dirty("uselogscale") + self.state.dirty("override_range") + + def generate_colorbar_image(self, index): + """Generate colorbar image for a variable at given index""" + if index >= len(self.state.variables): + return + + var = self.state.variables[index] + context = self.registry.get_view(var) + if context is None: + return + + # Get the ParaView color transfer function + coltrfunc = GetColorTransferFunction(var) + + # Store current state + current_use_log = coltrfunc.UseLogScale + + # Reset to linear scale and original range for image generation + if current_use_log: + coltrfunc.MapControlPointsToLinearSpace() + coltrfunc.UseLogScale = 0 + + # Apply the colormap preset to get clean colors + coltrfunc.ApplyPreset(context.config.colormap, True) + + # Generate the colorbar image with only inversion applied + try: + image_data = build_colorbar_image( + coltrfunc, + log_scale=False, # Always False for image generation + invert=context.config.invert_colors, + ) + # Update state with the new image without context manager to avoid recursive flush + self.state.colorbar_images[index] = image_data + self.state.dirty("colorbar_images") + except Exception as e: + print(f"Error generating colorbar image for {var}: {e}") + finally: + # Restore the log scale state if it was enabled + if current_use_log: + coltrfunc.MapControlPointsToLogSpace() + coltrfunc.UseLogScale = 1 + # Restore the range if it was modified + if context.config.override_range: + coltrfunc.RescaleTransferFunction( + context.config.min_value, context.config.max_value + ) def reset_camera(self, **kwargs): for widget in self.widgets: @@ -517,9 +570,15 @@ def rebuild_visualization_layout(self, cached_layout=None): config = ViewConfiguration( variable=var, - colormap=state.varcolor[0], - use_log_scale=False, - invert_colors=False, + colormap=state.varcolor[index] + if index < len(state.varcolor) + else state.varcolor[0], + use_log_scale=state.uselogscale[index] + if index < len(state.uselogscale) + else False, + invert_colors=state.invert[index] + if index < len(state.invert) + else False, min_value=varrange[0], max_value=varrange[1], override_range=override, @@ -535,6 +594,7 @@ def rebuild_visualization_layout(self, cached_layout=None): self.configure_new_view(var, context, self.source.views) context.index = index self.sync_color_config_to_state(index, context) + self.generate_colorbar_image(index) if index == 0: view0 = view @@ -573,36 +633,66 @@ async def flushViews(self): self.render_all_views() """ - def update_view_color_settings(self, index, type, value): + def update_colormap(self, index, value): + """Update the colormap for a variable.""" var = self.state.variables[index] coltrfunc = GetColorTransferFunction(var) - context: ViewContext = self.registry.get_view(var) - if type == EventType.COL.value: - context.config.colormap = value - coltrfunc.ApplyPreset(context.config.colormap, True) - elif type == EventType.LOG.value: - context.config.use_log_scale = value - if context.config.use_log_scale: - coltrfunc.MapControlPointsToLogSpace() - coltrfunc.UseLogScale = 1 - else: - coltrfunc.MapControlPointsToLinearSpace() - coltrfunc.UseLogScale = 0 - elif type == EventType.INV.value: - context.config.invert_colors = value + + context.config.colormap = value + # Generate new colorbar image BEFORE applying any transformations + self.generate_colorbar_image(index) + # Now apply the preset with current transformations + coltrfunc.ApplyPreset(context.config.colormap, True) + # Reapply inversion if it was enabled + if context.config.invert_colors: coltrfunc.InvertTransferFunction() + + # Sync all color configuration changes back to state + self.sync_color_config_to_state(index, context) + self.render_view_by_index(index) + + def update_log_scale(self, index, value): + """Update the log scale setting for a variable.""" + var = self.state.variables[index] + coltrfunc = GetColorTransferFunction(var) + context: ViewContext = self.registry.get_view(var) + + context.config.use_log_scale = value + if context.config.use_log_scale: + coltrfunc.MapControlPointsToLogSpace() + coltrfunc.UseLogScale = 1 + else: + coltrfunc.MapControlPointsToLinearSpace() + coltrfunc.UseLogScale = 0 + # Regenerate colorbar after log scale change + self.generate_colorbar_image(index) + + # Sync all color configuration changes back to state + self.sync_color_config_to_state(index, context) + self.render_view_by_index(index) + + def update_invert_colors(self, index, value): + """Update the color inversion setting for a variable.""" + var = self.state.variables[index] + coltrfunc = GetColorTransferFunction(var) + context: ViewContext = self.registry.get_view(var) + + context.config.invert_colors = value + coltrfunc.InvertTransferFunction() + # Generate new colorbar image when colors are inverted + self.generate_colorbar_image(index) + + # Sync all color configuration changes back to state + self.sync_color_config_to_state(index, context) self.render_view_by_index(index) def update_scalar_bars(self, event): + # Always hide ParaView scalar bars - using custom HTML colorbar + # The HTML colorbar is always visible, no toggle needed for var, context in self.registry.items(): view = context.state.view_proxy - context.state.data_representation.SetScalarBarVisibility(view, event) - coltrfunc = GetColorTransferFunction(var) - coltrfunc.ApplyPreset(context.config.colormap, True) - LUTColorBar = GetScalarBar(coltrfunc, view) - LUTColorBar.Title = "" - LUTColorBar.ComponentTitle = "" + context.state.data_representation.SetScalarBarVisibility(view, False) self.render_all_views() def set_manual_color_range(self, index, min, max): @@ -611,11 +701,13 @@ def set_manual_color_range(self, index, min, max): context.config.override_range = True context.config.min_value = float(min) context.config.max_value = float(max) - # Update state to reflect manual override - self.state.override_range[index] = True - self.state.dirty("override_range") + # Sync all changes back to state + self.sync_color_config_to_state(index, context) + # Update color transfer function coltrfunc = GetColorTransferFunction(var) coltrfunc.RescaleTransferFunction(float(min), float(max)) + # Generate new colorbar image with updated range + self.generate_colorbar_image(index) self.render_view_by_index(index) def revert_to_auto_color_range(self, index): @@ -626,15 +718,14 @@ def revert_to_auto_color_range(self, index): context.config.override_range = False context.config.min_value = varrange[0] context.config.max_value = varrange[1] - self.state.varmin[index] = context.config.min_value - self.state.dirty("varmin") - self.state.varmax[index] = context.config.max_value - self.state.dirty("varmax") - self.state.override_range[index] = context.config.override_range - self.state.dirty("override_range") + # Sync all changes back to state + self.sync_color_config_to_state(index, context) + # Rescale transfer function to data range context.state.data_representation.RescaleTransferFunctionToDataRange( False, True ) + # Generate new colorbar image with updated range + self.generate_colorbar_image(index) self.render_all_views() def zoom_in(self, index=0): diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 191801b..11fbb18 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "app" -version = "0.1.3" +version = "0.1.5" 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 7348f2b..d02f94a 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -7,7 +7,7 @@ }, "package": { "productName": "QuickView", - "version": "0.1.3" + "version": "0.1.5" }, "tauri": { "allowlist": {