Skip to content

Commit bdc32ec

Browse files
committed
refactor: replace embedded colormap code with trame-colormaps
QuickView previously contained ~1,400 lines of hand-rolled colormap state management, color transfer function algorithms, log/symlog/discrete transforms, tick computation, and colorbar/control-panel UI spread across view_manager.py, components/view.py, app.py, utils/color.py, and utils/math.py. All of that functionality is now provided by the external trame-colormaps package (pip-installable). The integration reduces to a handful of imports and a single widget call per view: - ColormapController wires the VTK mapper, manages presets and range - create_colorbar(orientation='horizontal') renders the full colorbar with its popup control panel Removed colormap functionality from: - view_manager.py (-800 lines): colormap state, controller wiring, range/preset logic - components/view.py (-209 lines): create_bottom_bar colorbar widget - utils/color.py (-125 lines, deleted): color transfer function helpers - utils/math.py (-265 lines): tick computation and scale transform math - app.py (-25 lines): colormap initialization and state serialization Added: - pyproject.toml: trame-colormaps>=0.1.0 dependency - ViewConfiguration fields matching the trame-colormaps API
1 parent fbe14eb commit bdc32ec

6 files changed

Lines changed: 53 additions & 1375 deletions

File tree

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ dependencies = [
2929
"trame-components",
3030
"trame-tauri>=0.6.2",
3131
"Pillow",
32+
"trame-colormaps>=0.1.0",
3233
]
3334

3435
[project.optional-dependencies]

src/e3sm_quickview/app.py

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
from e3sm_quickview.components import css, dialogs, doc, drawers, file_browser, toolbars
1919
from e3sm_quickview.pipeline import EAMVisSource
2020
from e3sm_quickview.utils import cli, compute, perf
21+
from e3sm_quickview.utils.colors import get_type_color
2122
from e3sm_quickview.view_manager import ViewManager
2223

2324
v3.enable_lab()
@@ -52,7 +53,7 @@ def __init__(self, server=None):
5253
"variables_selected": [],
5354
# Control 'Load Variables' button availability
5455
"variables_loaded": False,
55-
# Dynamic type-color mapping (populated when data loads)
56+
# Dimension type → Vuetify color mapping via utils/colors.py
5657
"variable_types": [],
5758
# Dimension arrays (will be populated dynamically)
5859
"midpoints": [],
@@ -338,23 +339,22 @@ def download_state(self):
338339
"type": view_type,
339340
"name": var_name,
340341
"config": {
341-
# lut
342+
# colormaps module
342343
"preset": config.preset,
343344
"invert": config.invert,
344345
"color_blind": config.color_blind,
345346
"use_log_scale": config.use_log_scale,
346347
"discrete_log": config.discrete_log,
347348
"n_discrete_colors": config.n_discrete_colors,
348-
# layout
349-
"order": config.order,
350-
"size": config.size,
351-
"offset": config.offset,
352-
"break_row": config.break_row,
353-
# color range
354349
"override_range": config.override_range,
355350
"color_range": config.color_range,
356351
"color_value_min": config.color_value_min,
357352
"color_value_max": config.color_value_max,
353+
# view layout
354+
"order": config.order,
355+
"size": config.size,
356+
"offset": config.offset,
357+
"break_row": config.break_row,
358358
},
359359
}
360360
)
@@ -409,6 +409,7 @@ async def _import_state(self, state_content):
409409
view_type = view_state["type"]
410410
var_name = view_state["name"]
411411
cfg = view_state["config"]
412+
# colormaps module: JSON deserializes tuples as lists
412413
if "color_range" in cfg and isinstance(cfg["color_range"], list):
413414
cfg["color_range"] = tuple(cfg["color_range"])
414415
config = self.view_manager.get_view(var_name, view_type).config
@@ -470,9 +471,7 @@ async def data_loading_open(self, simulation, connectivity):
470471
),
471472
]
472473

473-
# Build dynamic type-color mapping
474-
from e3sm_quickview.utils.colors import get_type_color
475-
474+
# Dimension type → Vuetify color mapping via utils/colors.py
476475
dim_types = sorted(
477476
set(
478477
", ".join(var.dimensions)
@@ -618,7 +617,7 @@ def _on_slicing_change(self, var, ind_var, **_):
618617
self.source.UpdatePipeline()
619618

620619
with perf.timed("tick.color_range"):
621-
self.view_manager.update_color_range()
620+
self.view_manager.update_color_range() # colormaps module
622621
with perf.timed("tick.render"):
623622
self.view_manager.render()
624623

@@ -656,7 +655,7 @@ def _on_downstream_change(
656655
self.source.UpdatePipeline()
657656

658657
with perf.timed("downstream_change.color_range"):
659-
self.view_manager.update_color_range()
658+
self.view_manager.update_color_range() # colormaps module
660659
with perf.timed("downstream_change.render"):
661660
self.view_manager.render()
662661

src/e3sm_quickview/components/view.py

Lines changed: 0 additions & 209 deletions
Original file line numberDiff line numberDiff line change
@@ -107,212 +107,3 @@ def create_size_menu(name, config):
107107
)
108108

109109

110-
def create_bottom_bar(config, update_color_preset):
111-
with config.provide_as("config"):
112-
with html.Div(
113-
classes="bg-blue-grey-darken-2 d-flex align-center",
114-
style="height:1rem;position:relative;top:0;user-select:none;cursor:context-menu;",
115-
):
116-
with v3.VMenu(
117-
v_model="config.menu",
118-
activator="parent",
119-
location=(
120-
"active_layout !== 'auto_layout' || config.size == 12 ? 'top' : 'end'",
121-
),
122-
close_on_content_click=False,
123-
):
124-
with v3.VCard(style="max-width: 360px;min-width: 360px;"):
125-
with v3.VCardItem(classes="py-0 px-2"):
126-
with html.Div(classes="d-flex align-center"):
127-
v3.VIconBtn(
128-
v_tooltip_bottom=(
129-
"config.color_blind ? 'Toggle for all color presets' : 'Toggle for colorblind safe color presets'",
130-
),
131-
icon=(
132-
"config.color_blind ? 'mdi-shield-check-outline' : 'mdi-palette'",
133-
),
134-
click="config.color_blind = !config.color_blind",
135-
size="small",
136-
text=(
137-
"config.color_blind ? 'Colorblind Safe' : 'All Colors'",
138-
),
139-
variant="text",
140-
)
141-
v3.VIconBtn(
142-
v_tooltip_bottom=(
143-
"config.invert ? 'Toggle to normal preset' : 'Toggle to inverted preset'",
144-
),
145-
icon=(
146-
"config.invert ? 'mdi-invert-colors' : 'mdi-invert-colors-off'",
147-
),
148-
click="config.invert = !config.invert",
149-
size="small",
150-
text=(
151-
"config.invert ? 'Inverted Preset' : 'Normal Preset'",
152-
),
153-
variant="text",
154-
)
155-
v3.VIconBtn(
156-
v_tooltip_bottom=(
157-
"config.use_log_scale === 'linear' ? 'Toggle to log scale' : config.use_log_scale === 'log' ? 'Toggle to symlog scale' : 'Toggle to linear scale'",
158-
),
159-
icon=(
160-
"config.use_log_scale === 'log' ? 'mdi-math-log' : config.use_log_scale === 'symlog' ? 'mdi-sine-wave mdi-rotate-330' : 'mdi-stairs'",
161-
),
162-
click="config.use_log_scale = config.use_log_scale === 'linear' ? 'log' : config.use_log_scale === 'log' ? 'symlog' : 'linear'",
163-
size="small",
164-
text=(
165-
"config.use_log_scale === 'log' ? 'Log' : config.use_log_scale === 'symlog' ? 'SymLog' : 'Linear'",
166-
),
167-
variant="text",
168-
)
169-
v3.VIconBtn(
170-
v_tooltip_bottom=(
171-
"config.override_range ? 'Toggle to use data range' : 'Toggle to use custom range'",
172-
),
173-
icon=(
174-
"config.override_range ? 'mdi-arrow-expand-horizontal' : 'mdi-pencil'",
175-
),
176-
click="config.override_range = !config.override_range",
177-
size="small",
178-
text=(
179-
"config.override_range ? 'Custom Range' : 'Data Range'",
180-
),
181-
variant="text",
182-
)
183-
v3.VIconBtn(
184-
v_tooltip_bottom=(
185-
"config.discrete_log ? 'Switch to continuous colormap' : 'Switch to discrete colormap'",
186-
),
187-
icon=(
188-
"config.discrete_log ? 'mdi-view-sequential' : 'mdi-gradient-horizontal'",
189-
),
190-
click="config.discrete_log = !config.discrete_log",
191-
size="small",
192-
text=(
193-
"config.discrete_log ? 'Discrete' : 'Continuous'",
194-
),
195-
variant="text",
196-
)
197-
198-
v3.VTextField(
199-
v_model="config.search",
200-
clearable=True,
201-
placeholder=("config.preset",),
202-
click_clear="config.search = null",
203-
single_line=True,
204-
variant="solo",
205-
density="compact",
206-
flat=True,
207-
hide_details="auto",
208-
# style="min-width: 150px;",
209-
classes="d-inline",
210-
reverse=True,
211-
)
212-
v3.VIconBtn(
213-
icon="mdi-close",
214-
size="small",
215-
text="Close",
216-
click="config.menu=false",
217-
)
218-
219-
with v3.VCardItem(
220-
v_show="config.discrete_log",
221-
classes="py-0 mb-2",
222-
):
223-
v3.VNumberInput(
224-
v_model="config.n_discrete_colors",
225-
hide_details=True,
226-
density="compact",
227-
variant="outlined",
228-
flat=True,
229-
label=(
230-
"config.use_log_scale === 'linear' ? 'Colors per tick interval' : 'Colors per order of magnitude'",
231-
),
232-
classes="mt-2",
233-
step=[1],
234-
min=[1],
235-
max=[20],
236-
)
237-
with v3.VCardItem(
238-
v_show="config.override_range", classes="py-0 mb-2"
239-
):
240-
v3.VTextField(
241-
v_model="config.color_value_min",
242-
hide_details=True,
243-
density="compact",
244-
variant="outlined",
245-
flat=True,
246-
label="Min",
247-
classes="mt-2",
248-
error=("!config.color_value_min_valid",),
249-
)
250-
v3.VTextField(
251-
v_model="config.color_value_max",
252-
hide_details=True,
253-
density="compact",
254-
variant="outlined",
255-
flat=True,
256-
label="Max",
257-
classes="mt-2",
258-
error=("!config.color_value_max_valid",),
259-
)
260-
v3.VDivider()
261-
with v3.VList(density="compact", max_height="40vh"):
262-
with v3.VListItem(
263-
v_for="entry in (config.invert ? luts_inverted : luts_normal)",
264-
v_show="(config.search?.length ? entry.name.toLowerCase().includes(config.search.toLowerCase()) : 1) && (!config.color_blind || entry.safe)",
265-
key="entry.name",
266-
subtitle=("entry.name",),
267-
click=(
268-
update_color_preset,
269-
"[entry.name, config.invert, config.use_log_scale, config.discrete_log, config.n_discrete_colors, config.n_colors]",
270-
),
271-
active=("config.preset === entry.name",),
272-
):
273-
html.Img(
274-
src=("entry.url",),
275-
style="width:100%;min-width:20rem;height:1rem;",
276-
classes="rounded",
277-
)
278-
html.Div(
279-
"{{ utils.quickview.formatRange(config.color_range?.[0], config.use_log_scale, config.color_range?.[0], config.color_range?.[1]) }}",
280-
classes="text-caption px-2 text-no-wrap",
281-
)
282-
with html.Div(
283-
classes="rounded w-100",
284-
style="height:70%;position:relative;",
285-
):
286-
html.Img(
287-
src=("config.lut_img",),
288-
style="width:100%;height:2rem;",
289-
draggable=False,
290-
)
291-
with html.Div(
292-
style="position:absolute;top:0;left:0;right:0;bottom:0;pointer-events:none;",
293-
):
294-
with html.Div(
295-
v_for="(tick, i) in config.color_ticks",
296-
key="i",
297-
style=(
298-
"`position:absolute;left:${tick.position}%;top:0;height:100%;transform:translateX(-50%);display:flex;flex-direction:column;align-items:center;`",
299-
),
300-
):
301-
html.Div(
302-
style=(
303-
"`width:1.5px;height:30%;background:${tick.color};`",
304-
),
305-
)
306-
html.Span(
307-
"{{ tick.label }}",
308-
style=(
309-
"`font-size:0.5rem;line-height:1;white-space:nowrap;color:${tick.color};`",
310-
),
311-
)
312-
html.Div(
313-
style=("`width:1.5px;flex:1;background:${tick.color};`",),
314-
)
315-
html.Div(
316-
"{{ utils.quickview.formatRange(config.color_range?.[1], config.use_log_scale, config.color_range?.[0], config.color_range?.[1]) }}",
317-
classes="text-caption px-2 text-no-wrap",
318-
)

0 commit comments

Comments
 (0)