Skip to content
Open
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
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ dependencies = [
"trame-components",
"trame-tauri>=0.6.2",
"Pillow",
"trame-colormaps>=1.0.0",
]

[project.optional-dependencies]
Expand Down
66 changes: 41 additions & 25 deletions src/e3sm_quickview/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from e3sm_quickview.components import css, dialogs, doc, drawers, file_browser, toolbars
from e3sm_quickview.pipeline import EAMVisSource
from e3sm_quickview.utils import cli, compute, perf
from e3sm_quickview.utils.colors import get_type_color
from e3sm_quickview.view_manager import ViewManager

v3.enable_lab()
Expand Down Expand Up @@ -52,7 +53,7 @@ def __init__(self, server=None):
"variables_selected": [],
# Control 'Load Variables' button availability
"variables_loaded": False,
# Dynamic type-color mapping (populated when data loads)
# Dimension type → Vuetify color mapping via utils/colors.py
"variable_types": [],
# Dimension arrays (will be populated dynamically)
"midpoints": [],
Expand Down Expand Up @@ -332,29 +333,31 @@ def download_state(self):
views_to_export = state_content["views"] = []
for view_type, var_names in active_variables.items():
for var_name in var_names:
config = self.view_manager.get_view(var_name, view_type).config
view = self.view_manager.get_view(var_name, view_type)
config = view.config
cmap = view.colormap
views_to_export.append(
{
"type": view_type,
"name": var_name,
"config": {
# lut
"preset": config.preset,
"invert": config.invert,
"color_blind": config.color_blind,
"use_log_scale": config.use_log_scale,
"discrete_log": config.discrete_log,
"n_discrete_colors": config.n_discrete_colors,
# layout
# view layout
"order": config.order,
"size": config.size,
"offset": config.offset,
"break_row": config.break_row,
# color range
"override_range": config.override_range,
"color_range": config.color_range,
"color_value_min": config.color_value_min,
"color_value_max": config.color_value_max,
},
"colormap": {
"preset": cmap.preset,
"invert": cmap.invert,
"color_blind": cmap.color_blind,
"use_log_scale": cmap.use_log_scale,
"discrete_log": cmap.discrete_log,
"n_discrete_colors": cmap.n_discrete_colors,
"override_range": cmap.override_range,
"color_range": cmap.color_range,
"color_value_min": cmap.color_value_min,
"color_value_max": cmap.color_value_max,
},
}
)
Expand Down Expand Up @@ -405,14 +408,29 @@ async def _import_state(self, state_content):
self.state.animation_track = data_sel["animation_track"]

# Update view states
_COLORMAP_KEYS = {
"preset", "invert", "color_blind", "use_log_scale",
"discrete_log", "n_discrete_colors", "override_range",
"color_range", "color_value_min", "color_value_max",
}
for view_state in state_content["views"]:
view_type = view_state["type"]
var_name = view_state["name"]
cfg = view_state["config"]
if "color_range" in cfg and isinstance(cfg["color_range"], list):
cfg["color_range"] = tuple(cfg["color_range"])
config = self.view_manager.get_view(var_name, view_type).config
config.update(**cfg)
view = self.view_manager.get_view(var_name, view_type)

cfg = dict(view_state["config"])
# Backward compat: old state files store colormap fields in "config"
cmap_cfg = dict(view_state.get("colormap", {}))
if not cmap_cfg:
cmap_cfg = {k: cfg.pop(k) for k in list(cfg) if k in _COLORMAP_KEYS}

# Layout config
view.config.update(**cfg)
# Colormap config
if "color_range" in cmap_cfg and isinstance(cmap_cfg["color_range"], list):
cmap_cfg["color_range"] = tuple(cmap_cfg["color_range"])
if cmap_cfg:
view.colormap.update(**cmap_cfg)

# Update layout
self.state.aspect_ratio = state_content["layout"]["aspect-ratio"]
Expand Down Expand Up @@ -470,9 +488,7 @@ async def data_loading_open(self, simulation, connectivity):
),
]

# Build dynamic type-color mapping
from e3sm_quickview.utils.colors import get_type_color

# Dimension type → Vuetify color mapping via utils/colors.py
dim_types = sorted(
set(
", ".join(var.dimensions)
Expand Down Expand Up @@ -618,7 +634,7 @@ def _on_slicing_change(self, var, ind_var, **_):
self.source.UpdatePipeline()

with perf.timed("tick.color_range"):
self.view_manager.update_color_range()
self.view_manager.update_color_range() # colormaps module
with perf.timed("tick.render"):
self.view_manager.render()

Expand Down Expand Up @@ -656,7 +672,7 @@ def _on_downstream_change(
self.source.UpdatePipeline()

with perf.timed("downstream_change.color_range"):
self.view_manager.update_color_range()
self.view_manager.update_color_range() # colormaps module
with perf.timed("downstream_change.render"):
self.view_manager.render()

Expand Down
209 changes: 0 additions & 209 deletions src/e3sm_quickview/components/view.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,212 +107,3 @@ def create_size_menu(name, config):
)


def create_bottom_bar(config, update_color_preset):
with config.provide_as("config"):
with html.Div(
classes="bg-blue-grey-darken-2 d-flex align-center",
style="height:1rem;position:relative;top:0;user-select:none;cursor:context-menu;",
):
with v3.VMenu(
v_model="config.menu",
activator="parent",
location=(
"active_layout !== 'auto_layout' || config.size == 12 ? 'top' : 'end'",
),
close_on_content_click=False,
):
with v3.VCard(style="max-width: 360px;min-width: 360px;"):
with v3.VCardItem(classes="py-0 px-2"):
with html.Div(classes="d-flex align-center"):
v3.VIconBtn(
v_tooltip_bottom=(
"config.color_blind ? 'Toggle for all color presets' : 'Toggle for colorblind safe color presets'",
),
icon=(
"config.color_blind ? 'mdi-shield-check-outline' : 'mdi-palette'",
),
click="config.color_blind = !config.color_blind",
size="small",
text=(
"config.color_blind ? 'Colorblind Safe' : 'All Colors'",
),
variant="text",
)
v3.VIconBtn(
v_tooltip_bottom=(
"config.invert ? 'Toggle to normal preset' : 'Toggle to inverted preset'",
),
icon=(
"config.invert ? 'mdi-invert-colors' : 'mdi-invert-colors-off'",
),
click="config.invert = !config.invert",
size="small",
text=(
"config.invert ? 'Inverted Preset' : 'Normal Preset'",
),
variant="text",
)
v3.VIconBtn(
v_tooltip_bottom=(
"config.use_log_scale === 'linear' ? 'Toggle to log scale' : config.use_log_scale === 'log' ? 'Toggle to symlog scale' : 'Toggle to linear scale'",
),
icon=(
"config.use_log_scale === 'log' ? 'mdi-math-log' : config.use_log_scale === 'symlog' ? 'mdi-sine-wave mdi-rotate-330' : 'mdi-stairs'",
),
click="config.use_log_scale = config.use_log_scale === 'linear' ? 'log' : config.use_log_scale === 'log' ? 'symlog' : 'linear'",
size="small",
text=(
"config.use_log_scale === 'log' ? 'Log' : config.use_log_scale === 'symlog' ? 'SymLog' : 'Linear'",
),
variant="text",
)
v3.VIconBtn(
v_tooltip_bottom=(
"config.override_range ? 'Toggle to use data range' : 'Toggle to use custom range'",
),
icon=(
"config.override_range ? 'mdi-arrow-expand-horizontal' : 'mdi-pencil'",
),
click="config.override_range = !config.override_range",
size="small",
text=(
"config.override_range ? 'Custom Range' : 'Data Range'",
),
variant="text",
)
v3.VIconBtn(
v_tooltip_bottom=(
"config.discrete_log ? 'Switch to continuous colormap' : 'Switch to discrete colormap'",
),
icon=(
"config.discrete_log ? 'mdi-view-sequential' : 'mdi-gradient-horizontal'",
),
click="config.discrete_log = !config.discrete_log",
size="small",
text=(
"config.discrete_log ? 'Discrete' : 'Continuous'",
),
variant="text",
)

v3.VTextField(
v_model="config.search",
clearable=True,
placeholder=("config.preset",),
click_clear="config.search = null",
single_line=True,
variant="solo",
density="compact",
flat=True,
hide_details="auto",
# style="min-width: 150px;",
classes="d-inline",
reverse=True,
)
v3.VIconBtn(
icon="mdi-close",
size="small",
text="Close",
click="config.menu=false",
)

with v3.VCardItem(
v_show="config.discrete_log",
classes="py-0 mb-2",
):
v3.VNumberInput(
v_model="config.n_discrete_colors",
hide_details=True,
density="compact",
variant="outlined",
flat=True,
label=(
"config.use_log_scale === 'linear' ? 'Colors per tick interval' : 'Colors per order of magnitude'",
),
classes="mt-2",
step=[1],
min=[1],
max=[20],
)
with v3.VCardItem(
v_show="config.override_range", classes="py-0 mb-2"
):
v3.VTextField(
v_model="config.color_value_min",
hide_details=True,
density="compact",
variant="outlined",
flat=True,
label="Min",
classes="mt-2",
error=("!config.color_value_min_valid",),
)
v3.VTextField(
v_model="config.color_value_max",
hide_details=True,
density="compact",
variant="outlined",
flat=True,
label="Max",
classes="mt-2",
error=("!config.color_value_max_valid",),
)
v3.VDivider()
with v3.VList(density="compact", max_height="40vh"):
with v3.VListItem(
v_for="entry in (config.invert ? luts_inverted : luts_normal)",
v_show="(config.search?.length ? entry.name.toLowerCase().includes(config.search.toLowerCase()) : 1) && (!config.color_blind || entry.safe)",
key="entry.name",
subtitle=("entry.name",),
click=(
update_color_preset,
"[entry.name, config.invert, config.use_log_scale, config.discrete_log, config.n_discrete_colors, config.n_colors]",
),
active=("config.preset === entry.name",),
):
html.Img(
src=("entry.url",),
style="width:100%;min-width:20rem;height:1rem;",
classes="rounded",
)
html.Div(
"{{ utils.quickview.formatRange(config.color_range?.[0], config.use_log_scale, config.color_range?.[0], config.color_range?.[1]) }}",
classes="text-caption px-2 text-no-wrap",
)
with html.Div(
classes="rounded w-100",
style="height:70%;position:relative;",
):
html.Img(
src=("config.lut_img",),
style="width:100%;height:2rem;",
draggable=False,
)
with html.Div(
style="position:absolute;top:0;left:0;right:0;bottom:0;pointer-events:none;",
):
with html.Div(
v_for="(tick, i) in config.color_ticks",
key="i",
style=(
"`position:absolute;left:${tick.position}%;top:0;height:100%;transform:translateX(-50%);display:flex;flex-direction:column;align-items:center;`",
),
):
html.Div(
style=(
"`width:1.5px;height:30%;background:${tick.color};`",
),
)
html.Span(
"{{ tick.label }}",
style=(
"`font-size:0.5rem;line-height:1;white-space:nowrap;color:${tick.color};`",
),
)
html.Div(
style=("`width:1.5px;flex:1;background:${tick.color};`",),
)
html.Div(
"{{ utils.quickview.formatRange(config.color_range?.[1], config.use_log_scale, config.color_range?.[0], config.color_range?.[1]) }}",
classes="text-caption px-2 text-no-wrap",
)
Loading
Loading