matterviz 0.4.2 → 0.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/FilePicker.svelte +40 -50
- package/dist/api/optimade.js +10 -7
- package/dist/app.css +15 -0
- package/dist/brillouin/BrillouinZone.svelte +18 -9
- package/dist/brillouin/BrillouinZoneScene.svelte +20 -29
- package/dist/brillouin/compute.js +10 -16
- package/dist/brillouin/geometry.js +2 -2
- package/dist/chempot-diagram/ChemPotDiagram.svelte +18 -15
- package/dist/chempot-diagram/ChemPotDiagram2D.svelte +17 -113
- package/dist/chempot-diagram/ChemPotDiagram3D.svelte +104 -264
- package/dist/chempot-diagram/async-compute.svelte.js +12 -13
- package/dist/chempot-diagram/color.d.ts +21 -1
- package/dist/chempot-diagram/color.js +70 -1
- package/dist/chempot-diagram/compute.d.ts +7 -0
- package/dist/chempot-diagram/compute.js +85 -26
- package/dist/chempot-diagram/export.js +1 -6
- package/dist/chempot-diagram/temperature.js +9 -10
- package/dist/composition/BarChart.svelte +7 -9
- package/dist/composition/Composition.svelte +3 -8
- package/dist/composition/Formula.svelte +2 -2
- package/dist/composition/FormulaFilter.svelte +27 -93
- package/dist/composition/format.js +6 -8
- package/dist/composition/parse.js +50 -105
- package/dist/constants.d.ts +0 -1
- package/dist/constants.js +6 -17
- package/dist/convex-hull/ConvexHull2D.svelte +0 -2
- package/dist/convex-hull/ConvexHull3D.svelte +19 -47
- package/dist/convex-hull/ConvexHull4D.svelte +65 -95
- package/dist/convex-hull/StructurePopup.svelte +1 -1
- package/dist/convex-hull/gas-thermodynamics.js +21 -53
- package/dist/convex-hull/helpers.js +1 -7
- package/dist/convex-hull/thermodynamics.js +19 -27
- package/dist/coordination/CoordinationBarPlot.svelte +12 -21
- package/dist/element/BohrAtom.svelte +0 -1
- package/dist/element/ElementTile.svelte +26 -44
- package/dist/element/data.d.ts +2 -0
- package/dist/element/data.js +5 -1
- package/dist/element/index.d.ts +1 -1
- package/dist/element/index.js +1 -1
- package/dist/fermi-surface/FermiSurface.svelte +22 -23
- package/dist/fermi-surface/FermiSurface.svelte.d.ts +2 -6
- package/dist/fermi-surface/FermiSurfaceScene.svelte +32 -77
- package/dist/fermi-surface/compute.js +22 -55
- package/dist/fermi-surface/parse.js +38 -70
- package/dist/file-viewer/JsonBrowser.svelte +1043 -0
- package/dist/file-viewer/JsonBrowser.svelte.d.ts +9 -0
- package/dist/file-viewer/PlotPanel.svelte +350 -0
- package/dist/file-viewer/PlotPanel.svelte.d.ts +9 -0
- package/dist/file-viewer/TrajectoryWithDos.svelte +30 -0
- package/dist/file-viewer/TrajectoryWithDos.svelte.d.ts +10 -0
- package/dist/file-viewer/detect.d.ts +11 -0
- package/dist/file-viewer/detect.js +381 -0
- package/dist/file-viewer/eligibility.d.ts +4 -0
- package/dist/file-viewer/eligibility.js +53 -0
- package/dist/file-viewer/host-protocol.d.ts +50 -0
- package/dist/file-viewer/host-transfer.d.ts +25 -0
- package/dist/file-viewer/host-transfer.js +42 -0
- package/dist/file-viewer/index.d.ts +5 -0
- package/dist/file-viewer/index.js +5 -0
- package/dist/file-viewer/main.d.ts +44 -0
- package/dist/file-viewer/main.js +535 -0
- package/dist/file-viewer/parse.d.ts +9 -0
- package/dist/file-viewer/parse.js +170 -0
- package/dist/file-viewer/plot-utils.d.ts +24 -0
- package/dist/file-viewer/plot-utils.js +191 -0
- package/dist/file-viewer/types.d.ts +4 -0
- package/dist/file-viewer/types.js +5 -0
- package/dist/heatmap-matrix/HeatmapMatrix.svelte +57 -84
- package/dist/heatmap-matrix/HeatmapMatrixControls.svelte.d.ts +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/io/ExportPane.svelte +1 -1
- package/dist/io/decompress.d.ts +2 -0
- package/dist/io/decompress.js +3 -2
- package/dist/io/export.d.ts +7 -4
- package/dist/io/export.js +70 -18
- package/dist/io/file-drop.js +60 -35
- package/dist/io/url-drop.d.ts +2 -0
- package/dist/io/url-drop.js +28 -16
- package/dist/isosurface/Isosurface.svelte +687 -196
- package/dist/isosurface/Isosurface.svelte.d.ts +7 -1
- package/dist/isosurface/IsosurfaceControls.svelte +564 -85
- package/dist/isosurface/IsosurfaceControls.svelte.d.ts +1 -1
- package/dist/isosurface/VolumeSlice.svelte +224 -0
- package/dist/isosurface/VolumeSlice.svelte.d.ts +28 -0
- package/dist/isosurface/coloring.d.ts +20 -0
- package/dist/isosurface/coloring.js +118 -0
- package/dist/isosurface/geometry-worker-types.d.ts +38 -0
- package/dist/isosurface/geometry.worker.d.ts +1 -0
- package/dist/isosurface/geometry.worker.js +59 -0
- package/dist/isosurface/grid.d.ts +10 -0
- package/dist/isosurface/grid.js +31 -0
- package/dist/isosurface/index.d.ts +4 -0
- package/dist/isosurface/index.js +4 -0
- package/dist/isosurface/profile.d.ts +12 -0
- package/dist/isosurface/profile.js +19 -0
- package/dist/isosurface/sampling.d.ts +34 -0
- package/dist/isosurface/sampling.js +408 -0
- package/dist/isosurface/slice-rendering.d.ts +13 -0
- package/dist/isosurface/slice-rendering.js +79 -0
- package/dist/isosurface/slice.d.ts +22 -2
- package/dist/isosurface/slice.js +200 -116
- package/dist/isosurface/types.d.ts +24 -1
- package/dist/isosurface/types.js +120 -8
- package/dist/json-path.d.ts +4 -0
- package/dist/json-path.js +89 -0
- package/dist/labels.js +1 -1
- package/dist/layout/InfoCard.svelte +1 -3
- package/dist/layout/SettingsSection.svelte +52 -61
- package/dist/layout/SettingsSection.svelte.d.ts +6 -7
- package/dist/layout/ViewerChrome.svelte +7 -1
- package/dist/layout/json-tree/JsonNode.svelte +14 -48
- package/dist/layout/json-tree/JsonTree.svelte +48 -95
- package/dist/layout/json-tree/JsonValue.svelte +3 -18
- package/dist/layout/json-tree/utils.d.ts +0 -3
- package/dist/layout/json-tree/utils.js +50 -179
- package/dist/marching-cubes.d.ts +8 -1
- package/dist/marching-cubes.js +115 -48
- package/dist/math.d.ts +3 -2
- package/dist/math.js +29 -38
- package/dist/overlays/ContextMenu.svelte +15 -32
- package/dist/overlays/DraggablePane.svelte +39 -58
- package/dist/overlays/DraggablePane.svelte.d.ts +1 -1
- package/dist/overlays/GlassChip.svelte +3 -1
- package/dist/periodic-table/PeriodicTableControls.svelte +26 -52
- package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte +18 -34
- package/dist/phase-diagram/build-diagram.js +1 -6
- package/dist/phase-diagram/svg-to-diagram.js +9 -14
- package/dist/plot/bar/BarPlot.svelte +38 -42
- package/dist/plot/bar/SpacegroupBarPlot.svelte +18 -42
- package/dist/plot/box/BoxPlot.svelte +27 -23
- package/dist/plot/core/auto-place.d.ts +1 -1
- package/dist/plot/core/auto-place.js +1 -1
- package/dist/plot/core/data-transform.d.ts +1 -2
- package/dist/plot/core/data-transform.js +0 -10
- package/dist/plot/core/layout.d.ts +12 -5
- package/dist/plot/core/layout.js +37 -27
- package/dist/plot/core/pan-zoom.svelte.d.ts +2 -4
- package/dist/plot/core/pan-zoom.svelte.js +34 -17
- package/dist/plot/core/placed-tween.svelte.d.ts +3 -0
- package/dist/plot/core/placed-tween.svelte.js +68 -21
- package/dist/plot/core/svg.js +0 -1
- package/dist/plot/core/utils/hierarchy-chart.d.ts +1 -0
- package/dist/plot/core/utils/hierarchy-chart.js +12 -0
- package/dist/plot/core/utils/hierarchy-labels.js +3 -2
- package/dist/plot/histogram/Histogram.svelte +21 -26
- package/dist/plot/sankey/sankey.js +13 -9
- package/dist/plot/scatter/BinnedScatterPlot.svelte +59 -43
- package/dist/plot/scatter/ScatterPlot.svelte +67 -52
- package/dist/plot/scatter/adaptive-density.d.ts +1 -1
- package/dist/plot/scatter/adaptive-density.js +36 -20
- package/dist/plot/sunburst/Sunburst.svelte +20 -20
- package/dist/plot/treemap/Treemap.svelte +175 -60
- package/dist/plot/treemap/Treemap.svelte.d.ts +6 -0
- package/dist/plot/treemap/index.d.ts +1 -0
- package/dist/plot/treemap/labels.d.ts +38 -0
- package/dist/plot/treemap/labels.js +80 -0
- package/dist/plot/treemap/treemap.d.ts +4 -2
- package/dist/plot/treemap/treemap.js +5 -2
- package/dist/rdf/RdfPlot.svelte +17 -35
- package/dist/rdf/calc-rdf.js +59 -44
- package/dist/rdf/index.d.ts +0 -1
- package/dist/sanitize.js +84 -26
- package/dist/scene/SceneCamera.svelte +1 -1
- package/dist/scene/SceneCamera.svelte.d.ts +1 -1
- package/dist/scene/index.d.ts +1 -2
- package/dist/scene/index.js +1 -1
- package/dist/scene/{props.js → props.svelte.js} +15 -1
- package/dist/settings.js +159 -482
- package/dist/spectral/Bands.svelte +151 -158
- package/dist/spectral/BrillouinBandsDos.svelte +4 -19
- package/dist/spectral/Dos.svelte +26 -24
- package/dist/spectral/helpers.js +40 -105
- package/dist/structure/AtomLegend.svelte +8 -13
- package/dist/structure/AtomLegend.svelte.d.ts +2 -3
- package/dist/structure/CellSelect.svelte +35 -10
- package/dist/structure/Structure.svelte +177 -99
- package/dist/structure/Structure.svelte.d.ts +1 -1
- package/dist/structure/StructureCarousel.svelte +10 -3
- package/dist/structure/StructureControls.svelte +8 -26
- package/dist/structure/StructureControls.svelte.d.ts +1 -1
- package/dist/structure/StructureInfoPane.svelte +65 -76
- package/dist/structure/StructureScene.svelte +75 -109
- package/dist/structure/StructureScene.svelte.d.ts +3 -1
- package/dist/structure/StructureViewport.svelte +120 -55
- package/dist/structure/StructureViewport.svelte.d.ts +3 -1
- package/dist/structure/atom-properties.d.ts +1 -1
- package/dist/structure/atom-properties.js +4 -3
- package/dist/structure/bond-order-perception.js +3 -8
- package/dist/structure/bonding.d.ts +0 -1
- package/dist/structure/bonding.js +16 -23
- package/dist/structure/export.js +50 -74
- package/dist/structure/format-detect.js +5 -7
- package/dist/structure/index.js +6 -12
- package/dist/structure/parse.js +38 -100
- package/dist/structure/partial-occupancy.js +7 -4
- package/dist/structure/pbc.js +8 -10
- package/dist/structure/polyhedra.js +5 -4
- package/dist/structure/prop-groups.d.ts +85 -0
- package/dist/structure/prop-groups.js +18 -0
- package/dist/symmetry/SymmetryStats.svelte +0 -4
- package/dist/symmetry/WyckoffTable.svelte +5 -4
- package/dist/symmetry/cell-transform.js +5 -14
- package/dist/symmetry/index.js +6 -9
- package/dist/table/HeatmapTable.svelte +61 -103
- package/dist/table/ToggleMenu.svelte +166 -44
- package/dist/table/ToggleMenu.svelte.d.ts +1 -1
- package/dist/theme/embedded.d.ts +18 -0
- package/dist/theme/embedded.js +206 -0
- package/dist/theme/index.d.ts +4 -0
- package/dist/theme/index.js +1 -2
- package/dist/theme/themes.mjs +31 -60
- package/dist/tooltip/TooltipContent.svelte +1 -1
- package/dist/tooltip/TooltipContent.svelte.d.ts +1 -1
- package/dist/tooltip/index.d.ts +8 -1
- package/dist/trajectory/Trajectory.svelte +140 -120
- package/dist/trajectory/TrajectoryError.svelte +1 -66
- package/dist/trajectory/TrajectoryExportPane.svelte +0 -1
- package/dist/trajectory/TrajectoryInfoPane.svelte +55 -80
- package/dist/trajectory/TrajectoryInfoPane.svelte.d.ts +2 -1
- package/dist/trajectory/extract.js +19 -29
- package/dist/trajectory/format-detect.d.ts +2 -0
- package/dist/trajectory/format-detect.js +7 -5
- package/dist/trajectory/frame-reader.js +3 -4
- package/dist/trajectory/helpers.js +16 -49
- package/dist/trajectory/index.js +2 -8
- package/dist/trajectory/parse/ase.js +1 -1
- package/dist/trajectory/parse/index.d.ts +4 -3
- package/dist/trajectory/parse/index.js +12 -17
- package/dist/trajectory/parse/vaspout-h5.d.ts +0 -1
- package/dist/trajectory/parse/vaspout-h5.js +1 -4
- package/dist/trajectory/parse/xyz.js +48 -2
- package/dist/trajectory/plotting.js +14 -37
- package/dist/xrd/XrdPlot.svelte +43 -68
- package/dist/xrd/calc-xrd.js +22 -58
- package/dist/xrd/parse.js +13 -22
- package/package.json +40 -8
- package/readme.md +6 -5
- package/dist/scene/types.d.ts +0 -26
- package/dist/tooltip/types.d.ts +0 -8
- package/dist/trajectory/constants.d.ts +0 -6
- package/dist/trajectory/constants.js +0 -11
- /package/dist/{scene/types.js → file-viewer/host-protocol.js} +0 -0
- /package/dist/{tooltip/types.js → isosurface/geometry-worker-types.js} +0 -0
|
@@ -1,15 +1,30 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
// Controls panel for isosurface visualization settings (isovalue, opacity, colors, etc.)
|
|
3
|
-
//
|
|
3
|
+
// Single-isovalue mode preserves the classic UI; explicit-layers mode groups
|
|
4
|
+
// surfaces under their geometry-source volume and exposes cross-volume scalar
|
|
5
|
+
// coloring (color source, colormap, value range) per surface.
|
|
6
|
+
import Icon from '../Icon.svelte'
|
|
7
|
+
import type { D3InterpolateName } from '../colors'
|
|
4
8
|
import { format_num } from '../labels'
|
|
5
9
|
import { SettingsSection } from '../layout'
|
|
10
|
+
import type { Vec2 } from '../math'
|
|
11
|
+
import { ColorScaleSelect } from '../plot'
|
|
6
12
|
import { tooltip } from 'svelte-multiselect/attachments'
|
|
13
|
+
import { auto_color_config, DEFAULT_ISO_COLORMAP, ISO_COLORMAPS } from './coloring'
|
|
14
|
+
import type { DisplayRange } from './sampling'
|
|
15
|
+
import { compare_volume_grids } from './sampling'
|
|
7
16
|
import type { IsosurfaceLayer, IsosurfaceSettings, VolumetricData } from './types'
|
|
8
|
-
import {
|
|
17
|
+
import {
|
|
18
|
+
auto_volume_layer,
|
|
19
|
+
DEFAULT_ISOSURFACE_SETTINGS,
|
|
20
|
+
generate_layers,
|
|
21
|
+
materialize_layers,
|
|
22
|
+
remove_volume,
|
|
23
|
+
} from './types'
|
|
9
24
|
|
|
10
25
|
let {
|
|
11
26
|
settings = $bindable({ ...DEFAULT_ISOSURFACE_SETTINGS }),
|
|
12
|
-
volumes = [],
|
|
27
|
+
volumes = $bindable([]),
|
|
13
28
|
active_volume_idx = $bindable(0),
|
|
14
29
|
}: {
|
|
15
30
|
settings?: IsosurfaceSettings
|
|
@@ -31,15 +46,24 @@
|
|
|
31
46
|
|
|
32
47
|
let slider_max = $derived(Math.max(data_range.abs_max, 0.001))
|
|
33
48
|
let step = $derived(slider_max / 200)
|
|
34
|
-
|
|
49
|
+
// Explicit layers mode is active whenever layers is set — an empty array means
|
|
50
|
+
// "zero surfaces" (all removed), NOT a fallback to implicit single-surface mode
|
|
51
|
+
let is_multi_layer = $derived(settings.layers != null)
|
|
35
52
|
let n_layers = $derived(settings.layers?.length ?? 1)
|
|
36
53
|
|
|
54
|
+
const vol_label = (idx: number): string => volumes[idx]?.label ?? `Volume ${idx + 1}`
|
|
55
|
+
|
|
56
|
+
// Resolve a layer's geometry volume the same way for grouping, warnings, and
|
|
57
|
+
// data ranges (out-of-range indices clamp so stale layers stay editable/removable)
|
|
58
|
+
const resolve_geo_idx = (layer: IsosurfaceLayer): number =>
|
|
59
|
+
Math.min(Math.max(layer.volume_idx ?? active_volume_idx, 0), volumes.length - 1)
|
|
60
|
+
|
|
61
|
+
// The layer's scalar-color-source volume, if any
|
|
62
|
+
const color_vol_of = (layer: IsosurfaceLayer): VolumetricData | undefined =>
|
|
63
|
+
layer.color_volume_idx != null ? volumes[layer.color_volume_idx] : undefined
|
|
64
|
+
|
|
37
65
|
function set_layer_count(count: number) {
|
|
38
|
-
|
|
39
|
-
settings.layers = undefined
|
|
40
|
-
} else {
|
|
41
|
-
settings.layers = generate_layers(data_range, count)
|
|
42
|
-
}
|
|
66
|
+
settings.layers = count <= 1 ? undefined : generate_layers(data_range, count)
|
|
43
67
|
}
|
|
44
68
|
|
|
45
69
|
function update_layer(idx: number, updates: Partial<IsosurfaceLayer>) {
|
|
@@ -48,8 +72,159 @@
|
|
|
48
72
|
layer_idx === idx ? { ...layer, ...updates } : layer,
|
|
49
73
|
)
|
|
50
74
|
}
|
|
75
|
+
|
|
76
|
+
function remove_layer(idx: number) {
|
|
77
|
+
if (settings.layers)
|
|
78
|
+
settings.layers = settings.layers.filter((_layer, layer_idx) => layer_idx !== idx)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function add_surface(vol_idx: number) {
|
|
82
|
+
const vol = volumes[vol_idx]
|
|
83
|
+
if (!vol) return
|
|
84
|
+
const layers = materialize_layers(settings, active_volume_idx)
|
|
85
|
+
layers.push(auto_volume_layer(vol, vol_idx, layers.length))
|
|
86
|
+
settings.layers = layers
|
|
87
|
+
active_volume_idx = vol_idx
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function handle_remove_volume(vol_idx: number) {
|
|
91
|
+
const layers = materialize_layers(settings, active_volume_idx)
|
|
92
|
+
const result = remove_volume(volumes, layers, vol_idx, active_volume_idx)
|
|
93
|
+
volumes = result.volumes
|
|
94
|
+
settings.layers = result.layers
|
|
95
|
+
// Keep the active volume pointing at the same physical volume (indices shift)
|
|
96
|
+
if (active_volume_idx > vol_idx) active_volume_idx -= 1
|
|
97
|
+
if (active_volume_idx >= result.volumes.length) active_volume_idx = 0
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Set (or clear) a layer's scalar-color source. The colormap is auto-picked
|
|
101
|
+
// from the color volume's data; color_range stays unset so the renderer fits
|
|
102
|
+
// it to the scalar values actually present on the surface.
|
|
103
|
+
function set_color_source(layer_idx: number, color_idx: number | null) {
|
|
104
|
+
const color_vol = color_idx === null ? undefined : volumes[color_idx]
|
|
105
|
+
if (color_idx !== null && !color_vol) return
|
|
106
|
+
update_layer(layer_idx, {
|
|
107
|
+
color_volume_idx: color_idx ?? undefined,
|
|
108
|
+
colormap: color_vol ? auto_color_config(color_vol.data_range).colormap : undefined,
|
|
109
|
+
color_range: undefined,
|
|
110
|
+
})
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Picking a color source in single-isovalue mode materializes explicit layers
|
|
114
|
+
function set_single_color_source(color_idx: number | null) {
|
|
115
|
+
if (color_idx === null) return
|
|
116
|
+
settings.layers = materialize_layers(settings, active_volume_idx)
|
|
117
|
+
set_color_source(0, color_idx)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Update one bound of a layer's color range. An empty input resets the whole
|
|
121
|
+
// range to auto-fit (renderer fits it to the values sampled on the surface).
|
|
122
|
+
// Typing into an auto range seeds the other bound from the color volume's
|
|
123
|
+
// full data range as a starting point.
|
|
124
|
+
function update_color_range(layer_idx: number, bound: 0 | 1, raw_value: string) {
|
|
125
|
+
const layer = settings.layers?.[layer_idx]
|
|
126
|
+
if (!layer) return
|
|
127
|
+
if (raw_value.trim() === ``) {
|
|
128
|
+
update_layer(layer_idx, { color_range: undefined })
|
|
129
|
+
return
|
|
130
|
+
}
|
|
131
|
+
const value = Number(raw_value)
|
|
132
|
+
if (Number.isNaN(value)) return
|
|
133
|
+
const color_vol = color_vol_of(layer)
|
|
134
|
+
const current: Vec2 =
|
|
135
|
+
layer.color_range ??
|
|
136
|
+
(color_vol ? auto_color_config(color_vol.data_range).color_range : [0, 1])
|
|
137
|
+
const next: Vec2 = bound === 0 ? [value, current[1]] : [current[0], value]
|
|
138
|
+
update_layer(layer_idx, { color_range: next })
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Grid-compatibility note for a surface/color volume pair. Strictly matching
|
|
142
|
+
// grids sample exactly; otherwise values are resampled in shared coordinates.
|
|
143
|
+
function compat_warning(layer: IsosurfaceLayer): string | null {
|
|
144
|
+
const geo_vol = volumes[resolve_geo_idx(layer)]
|
|
145
|
+
const color_vol = color_vol_of(layer)
|
|
146
|
+
if (!geo_vol || !color_vol || geo_vol === color_vol) return null
|
|
147
|
+
const compat = compare_volume_grids(geo_vol, color_vol)
|
|
148
|
+
return compat.ok ? null : (compat.reason ?? `grids differ`)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Layers grouped by geometry-source volume for the tree-style multi-layer UI
|
|
152
|
+
let grouped_layers = $derived.by(() => {
|
|
153
|
+
const groups = volumes.map((_vol, vol_idx) => ({
|
|
154
|
+
vol_idx,
|
|
155
|
+
entries: [] as { layer: IsosurfaceLayer; layer_idx: number }[],
|
|
156
|
+
}))
|
|
157
|
+
for (const [layer_idx, layer] of (settings.layers ?? []).entries()) {
|
|
158
|
+
const vol_idx = resolve_geo_idx(layer)
|
|
159
|
+
if (vol_idx >= 0) groups[vol_idx].entries.push({ layer, layer_idx })
|
|
160
|
+
}
|
|
161
|
+
return groups
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
// Halo pads geometry volumes only, so gate on volumes that actually render
|
|
165
|
+
// surfaces (color-source-only volumes don't count)
|
|
166
|
+
let any_periodic = $derived(
|
|
167
|
+
settings.layers
|
|
168
|
+
? settings.layers.some((layer) => volumes[resolve_geo_idx(layer)]?.periodic)
|
|
169
|
+
: (volumes[active_volume_idx]?.periodic ?? false),
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
// Update one bound of the fractional display range. Clearing an input resets
|
|
173
|
+
// that bound to its default (0 or 1); a fully default range unsets display_range
|
|
174
|
+
// so surfaces follow the structure's integer supercell again.
|
|
175
|
+
function update_display_range(axis: number, bound: 0 | 1, raw_value: string) {
|
|
176
|
+
const range = (settings.display_range?.map((pair) => [...pair]) ?? [
|
|
177
|
+
[0, 1],
|
|
178
|
+
[0, 1],
|
|
179
|
+
[0, 1],
|
|
180
|
+
]) as DisplayRange
|
|
181
|
+
const value = raw_value.trim() === `` ? bound : Number(raw_value)
|
|
182
|
+
if (Number.isNaN(value)) return
|
|
183
|
+
range[axis][bound] = value
|
|
184
|
+
const is_default = range.every(([lo, hi]) => lo === 0 && hi === 1)
|
|
185
|
+
settings.display_range = is_default ? undefined : range
|
|
186
|
+
}
|
|
51
187
|
</script>
|
|
52
188
|
|
|
189
|
+
{#snippet color_source_select(
|
|
190
|
+
selected: number,
|
|
191
|
+
on_pick: (idx: number | null) => void,
|
|
192
|
+
tip: string,
|
|
193
|
+
)}
|
|
194
|
+
<label {@attach tooltip({ content: tip })}>
|
|
195
|
+
<span>Color by:</span>
|
|
196
|
+
<select
|
|
197
|
+
value={selected}
|
|
198
|
+
onchange={(event) => {
|
|
199
|
+
const idx = Number(event.currentTarget.value)
|
|
200
|
+
on_pick(idx < 0 ? null : idx)
|
|
201
|
+
}}
|
|
202
|
+
>
|
|
203
|
+
<option value={-1}>None (solid)</option>
|
|
204
|
+
{#each volumes as _color_vol, color_idx (color_idx)}
|
|
205
|
+
<option value={color_idx}>{vol_label(color_idx)}</option>
|
|
206
|
+
{/each}
|
|
207
|
+
</select>
|
|
208
|
+
</label>
|
|
209
|
+
{/snippet}
|
|
210
|
+
|
|
211
|
+
{#snippet range_bound_input(layer_idx: number, bound: 0 | 1, explicit_range?: Vec2)}
|
|
212
|
+
<input
|
|
213
|
+
type="number"
|
|
214
|
+
class="range-input"
|
|
215
|
+
step="any"
|
|
216
|
+
placeholder="auto"
|
|
217
|
+
value={explicit_range ? Number(explicit_range[bound].toPrecision(4)) : ``}
|
|
218
|
+
onchange={(event) => update_color_range(layer_idx, bound, event.currentTarget.value)}
|
|
219
|
+
aria-label={bound === 0 ? `Color range minimum` : `Color range maximum`}
|
|
220
|
+
{@attach tooltip({
|
|
221
|
+
content: `Value mapped to the colormap ${
|
|
222
|
+
bound === 0 ? `start` : `end`
|
|
223
|
+
} (empty = auto-fit to surface values)`,
|
|
224
|
+
})}
|
|
225
|
+
/>
|
|
226
|
+
{/snippet}
|
|
227
|
+
|
|
53
228
|
<SettingsSection
|
|
54
229
|
title="Isosurface"
|
|
55
230
|
current_values={{
|
|
@@ -59,14 +234,15 @@
|
|
|
59
234
|
wireframe: settings.wireframe,
|
|
60
235
|
halo: settings.halo,
|
|
61
236
|
layers: n_layers,
|
|
237
|
+
display_range: settings.display_range?.flat().join(`,`) ?? ``,
|
|
62
238
|
}}
|
|
63
239
|
on_reset={() => {
|
|
64
240
|
settings = { ...DEFAULT_ISOSURFACE_SETTINGS }
|
|
65
241
|
}}
|
|
66
242
|
>
|
|
67
|
-
<!-- Top row: volume selector (
|
|
243
|
+
<!-- Top row: volume selector (single-layer multi-volume) + layer count + toggles -->
|
|
68
244
|
<div class="pane-row compact-row">
|
|
69
|
-
{#if volumes.length > 1}
|
|
245
|
+
{#if !is_multi_layer && volumes.length > 1}
|
|
70
246
|
<label
|
|
71
247
|
{@attach tooltip({
|
|
72
248
|
content: `Select which volume to display (e.g. charge vs magnetization)`,
|
|
@@ -74,27 +250,29 @@
|
|
|
74
250
|
>
|
|
75
251
|
<span>Volume:</span>
|
|
76
252
|
<select bind:value={active_volume_idx}>
|
|
77
|
-
{#each volumes as
|
|
78
|
-
<option value={idx}>{
|
|
253
|
+
{#each volumes as _vol, idx (idx)}
|
|
254
|
+
<option value={idx}>{vol_label(idx)}</option>
|
|
79
255
|
{/each}
|
|
80
256
|
</select>
|
|
81
257
|
</label>
|
|
82
258
|
{/if}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
<span>Layers:</span>
|
|
89
|
-
<select
|
|
90
|
-
value={n_layers}
|
|
91
|
-
onchange={(event) => set_layer_count(Number(event.currentTarget.value))}
|
|
259
|
+
{#if !is_multi_layer}
|
|
260
|
+
<label
|
|
261
|
+
{@attach tooltip({
|
|
262
|
+
content: `Number of isosurface shells at different density thresholds`,
|
|
263
|
+
})}
|
|
92
264
|
>
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
265
|
+
<span>Layers:</span>
|
|
266
|
+
<select
|
|
267
|
+
value={n_layers}
|
|
268
|
+
onchange={(event) => set_layer_count(Number(event.currentTarget.value))}
|
|
269
|
+
>
|
|
270
|
+
{#each [1, 2, 3, 4, 5] as count (count)}
|
|
271
|
+
<option value={count}>{count}</option>
|
|
272
|
+
{/each}
|
|
273
|
+
</select>
|
|
274
|
+
</label>
|
|
275
|
+
{/if}
|
|
98
276
|
<!-- Sync both settings.show_negative (single-layer fallback) and all layer entries
|
|
99
277
|
so the toggle works consistently regardless of which mode is active -->
|
|
100
278
|
<label
|
|
@@ -127,45 +305,160 @@
|
|
|
127
305
|
</div>
|
|
128
306
|
|
|
129
307
|
{#if is_multi_layer && settings.layers}
|
|
130
|
-
<!-- Multi-layer
|
|
131
|
-
{#each
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
308
|
+
<!-- Multi-layer mode: surfaces grouped under their geometry-source volume -->
|
|
309
|
+
{#each grouped_layers as { vol_idx, entries } (vol_idx)}
|
|
310
|
+
{@const vol = volumes[vol_idx]}
|
|
311
|
+
<div class="volume-group">
|
|
312
|
+
<div class="volume-header">
|
|
313
|
+
<span class="volume-label" title={vol_label(vol_idx)}>{vol_label(vol_idx)}</span>
|
|
314
|
+
<span class="volume-dims">{vol.grid_dims.join(`×`)}</span>
|
|
315
|
+
{#if entries.length === 0}
|
|
316
|
+
<span
|
|
317
|
+
class="volume-note"
|
|
318
|
+
{@attach tooltip({
|
|
319
|
+
content: `No surfaces — this volume is still available as a color source for other surfaces`,
|
|
320
|
+
})}>color source only</span
|
|
321
|
+
>
|
|
322
|
+
{/if}
|
|
323
|
+
<button
|
|
324
|
+
type="button"
|
|
325
|
+
class="icon-btn"
|
|
326
|
+
onclick={() => add_surface(vol_idx)}
|
|
327
|
+
aria-label="Add surface for {vol_label(vol_idx)}"
|
|
328
|
+
{@attach tooltip({ content: `Add an isosurface from this volume` })}>+</button
|
|
329
|
+
>
|
|
330
|
+
{#if volumes.length > 1}
|
|
331
|
+
<button
|
|
332
|
+
type="button"
|
|
333
|
+
class="icon-btn"
|
|
334
|
+
onclick={() => handle_remove_volume(vol_idx)}
|
|
335
|
+
aria-label="Remove volume {vol_label(vol_idx)}"
|
|
336
|
+
{@attach tooltip({ content: `Remove this volume and its surfaces` })}>×</button
|
|
337
|
+
>
|
|
338
|
+
{/if}
|
|
339
|
+
</div>
|
|
340
|
+
|
|
341
|
+
{#each entries as { layer, layer_idx } (layer_idx)}
|
|
342
|
+
{@const layer_abs_max = Math.max(vol.data_range.abs_max, 0.001)}
|
|
343
|
+
{@const layer_step = layer_abs_max / 200}
|
|
344
|
+
{@const warning = compat_warning(layer)}
|
|
345
|
+
{@const color_vol = color_vol_of(layer)}
|
|
346
|
+
<div class="layer-row">
|
|
347
|
+
<input
|
|
348
|
+
type="checkbox"
|
|
349
|
+
checked={layer.visible}
|
|
350
|
+
onchange={() => update_layer(layer_idx, { visible: !layer.visible })}
|
|
351
|
+
{@attach tooltip({ content: `Toggle surface visibility` })}
|
|
352
|
+
/>
|
|
353
|
+
<input
|
|
354
|
+
type="color"
|
|
355
|
+
value={layer.color}
|
|
356
|
+
onchange={(event) =>
|
|
357
|
+
update_layer(layer_idx, { color: event.currentTarget.value })}
|
|
358
|
+
{@attach tooltip({
|
|
359
|
+
content:
|
|
360
|
+
layer.color_volume_idx != null
|
|
361
|
+
? `Fallback color (surface uses colormap)`
|
|
362
|
+
: `Surface color`,
|
|
363
|
+
})}
|
|
364
|
+
/>
|
|
365
|
+
<label
|
|
366
|
+
class="slider-field"
|
|
367
|
+
{@attach tooltip({
|
|
368
|
+
content: `Density threshold — surface is drawn where grid values equal this`,
|
|
369
|
+
})}
|
|
370
|
+
>
|
|
371
|
+
<span>Iso</span>
|
|
372
|
+
<input
|
|
373
|
+
type="range"
|
|
374
|
+
class="isovalue-slider"
|
|
375
|
+
min={layer_step}
|
|
376
|
+
max={layer_abs_max}
|
|
377
|
+
step={layer_step}
|
|
378
|
+
value={layer.isovalue}
|
|
379
|
+
oninput={(event) =>
|
|
380
|
+
update_layer(layer_idx, { isovalue: Number(event.currentTarget.value) })}
|
|
381
|
+
aria-label="Isovalue"
|
|
382
|
+
/>
|
|
383
|
+
<span class="layer-value">{format_num(layer.isovalue, `.3~g`)}</span>
|
|
384
|
+
</label>
|
|
385
|
+
<label class="slider-field" {@attach tooltip({ content: `Surface transparency` })}>
|
|
386
|
+
<span>Op</span>
|
|
387
|
+
<input
|
|
388
|
+
type="range"
|
|
389
|
+
class="opacity-slider"
|
|
390
|
+
min={0.1}
|
|
391
|
+
max={1}
|
|
392
|
+
step={0.05}
|
|
393
|
+
value={layer.opacity}
|
|
394
|
+
oninput={(event) =>
|
|
395
|
+
update_layer(layer_idx, { opacity: Number(event.currentTarget.value) })}
|
|
396
|
+
aria-label="Opacity"
|
|
397
|
+
/>
|
|
398
|
+
<span class="layer-value">{format_num(layer.opacity, `.2f`)}</span>
|
|
399
|
+
</label>
|
|
400
|
+
<button
|
|
401
|
+
type="button"
|
|
402
|
+
class="icon-btn"
|
|
403
|
+
onclick={() => remove_layer(layer_idx)}
|
|
404
|
+
aria-label="Remove surface"
|
|
405
|
+
{@attach tooltip({ content: `Remove this surface` })}>×</button
|
|
406
|
+
>
|
|
407
|
+
</div>
|
|
408
|
+
<div class="color-row">
|
|
409
|
+
{@render color_source_select(
|
|
410
|
+
layer.color_volume_idx ?? -1,
|
|
411
|
+
(idx) => set_color_source(layer_idx, idx),
|
|
412
|
+
`Color surface by another volume's values`,
|
|
413
|
+
)}
|
|
414
|
+
{#if color_vol}
|
|
415
|
+
{@const explicit_range = layer.color_range}
|
|
416
|
+
{@const auto_colormap = auto_color_config(color_vol.data_range).colormap}
|
|
417
|
+
<ColorScaleSelect
|
|
418
|
+
options={[...ISO_COLORMAPS]}
|
|
419
|
+
value={layer.colormap ?? DEFAULT_ISO_COLORMAP}
|
|
420
|
+
selected={[layer.colormap ?? DEFAULT_ISO_COLORMAP]}
|
|
421
|
+
onadd={({ option }) =>
|
|
422
|
+
update_layer(layer_idx, {
|
|
423
|
+
colormap: option as D3InterpolateName,
|
|
424
|
+
})}
|
|
425
|
+
colorbar={{
|
|
426
|
+
bar_style: `height: 8px`,
|
|
427
|
+
title_style: `width: 4em; font-size: 1em;`,
|
|
428
|
+
}}
|
|
429
|
+
liSelectedStyle="width: 100%; margin: 0; padding: 0; background: transparent;"
|
|
430
|
+
aria-label="Colormap for sampled values"
|
|
431
|
+
{@attach tooltip({ content: `Colormap for sampled values` })}
|
|
432
|
+
/>
|
|
433
|
+
<div class="color-range" aria-label="Colormap value range">
|
|
434
|
+
<span>Range:</span>
|
|
435
|
+
{@render range_bound_input(layer_idx, 0, explicit_range)}
|
|
436
|
+
<span aria-hidden="true">–</span>
|
|
437
|
+
{@render range_bound_input(layer_idx, 1, explicit_range)}
|
|
438
|
+
</div>
|
|
439
|
+
{#if explicit_range || (layer.colormap ?? DEFAULT_ISO_COLORMAP) !== auto_colormap}
|
|
440
|
+
{@const reset_color_label = `Reset colormap + range to auto-fit`}
|
|
441
|
+
<button
|
|
442
|
+
type="button"
|
|
443
|
+
class="icon-btn"
|
|
444
|
+
onclick={() => set_color_source(layer_idx, layer.color_volume_idx ?? null)}
|
|
445
|
+
aria-label={reset_color_label}
|
|
446
|
+
{@attach tooltip({ content: reset_color_label })}
|
|
447
|
+
>
|
|
448
|
+
<Icon icon="Reset" aria-hidden="true" style="--icon-size: 12px" />
|
|
449
|
+
</button>
|
|
450
|
+
{/if}
|
|
451
|
+
{#if warning}
|
|
452
|
+
<span
|
|
453
|
+
class="compat-warning"
|
|
454
|
+
{@attach tooltip({
|
|
455
|
+
content: `Grids differ (${warning}) — values are resampled by trilinear interpolation in shared coordinates`,
|
|
456
|
+
})}>⚠</span
|
|
457
|
+
>
|
|
458
|
+
{/if}
|
|
459
|
+
{/if}
|
|
460
|
+
</div>
|
|
461
|
+
{/each}
|
|
169
462
|
</div>
|
|
170
463
|
{/each}
|
|
171
464
|
{:else}
|
|
@@ -177,7 +470,7 @@
|
|
|
177
470
|
>
|
|
178
471
|
<span>Isovalue:</span>
|
|
179
472
|
<input type="range" min={step} max={slider_max} {step} bind:value={settings.isovalue} />
|
|
180
|
-
<span class="value">{format_num(settings.isovalue, `.
|
|
473
|
+
<span class="value">{format_num(settings.isovalue, `.3~g`)}</span>
|
|
181
474
|
</label>
|
|
182
475
|
|
|
183
476
|
<!-- Opacity + colors on one row -->
|
|
@@ -208,10 +501,17 @@
|
|
|
208
501
|
<input type="color" bind:value={settings.negative_color} />
|
|
209
502
|
</label>
|
|
210
503
|
{/if}
|
|
504
|
+
{#if volumes.length > 1}
|
|
505
|
+
{@render color_source_select(
|
|
506
|
+
-1,
|
|
507
|
+
set_single_color_source,
|
|
508
|
+
`Color this surface by another volume's values (e.g. density by ESP)`,
|
|
509
|
+
)}
|
|
510
|
+
{/if}
|
|
211
511
|
</div>
|
|
212
512
|
{/if}
|
|
213
513
|
|
|
214
|
-
{#if
|
|
514
|
+
{#if any_periodic}
|
|
215
515
|
<label
|
|
216
516
|
{@attach tooltip({
|
|
217
517
|
content: `Extend isosurface beyond cell boundaries to close partial spheres (fraction of cell)`,
|
|
@@ -220,9 +520,46 @@
|
|
|
220
520
|
Halo: {format_num(settings.halo, `.2f`)}
|
|
221
521
|
<input type="range" min={0} max={0.5} step={0.01} bind:value={settings.halo} />
|
|
222
522
|
</label>
|
|
523
|
+
<div class="pane-row compact-row display-range">
|
|
524
|
+
<span
|
|
525
|
+
{@attach tooltip({
|
|
526
|
+
content: `Fractional display range per lattice vector (VESTA-style): repeats periodic surfaces and clips them exactly at these bounds, e.g. -0.15 to 2.15. Empty = follow the structure supercell.`,
|
|
527
|
+
})}>Range:</span
|
|
528
|
+
>
|
|
529
|
+
{#each [`a`, `b`, `c`] as axis_label, axis (axis)}
|
|
530
|
+
<label class="range-axis">
|
|
531
|
+
{axis_label}
|
|
532
|
+
<input
|
|
533
|
+
type="number"
|
|
534
|
+
step="0.05"
|
|
535
|
+
placeholder="0"
|
|
536
|
+
value={settings.display_range ? settings.display_range[axis][0] : ``}
|
|
537
|
+
onchange={(event) => update_display_range(axis, 0, event.currentTarget.value)}
|
|
538
|
+
/>
|
|
539
|
+
<input
|
|
540
|
+
type="number"
|
|
541
|
+
step="0.05"
|
|
542
|
+
placeholder="1"
|
|
543
|
+
value={settings.display_range ? settings.display_range[axis][1] : ``}
|
|
544
|
+
onchange={(event) => update_display_range(axis, 1, event.currentTarget.value)}
|
|
545
|
+
/>
|
|
546
|
+
</label>
|
|
547
|
+
{/each}
|
|
548
|
+
{#if settings.display_range}
|
|
549
|
+
<button
|
|
550
|
+
type="button"
|
|
551
|
+
class="icon-btn"
|
|
552
|
+
onclick={() => (settings.display_range = undefined)}
|
|
553
|
+
aria-label="Reset display range"
|
|
554
|
+
{@attach tooltip({ content: `Follow the structure supercell again` })}
|
|
555
|
+
>
|
|
556
|
+
<Icon icon="Reset" aria-hidden="true" style="--icon-size: 12px" />
|
|
557
|
+
</button>
|
|
558
|
+
{/if}
|
|
559
|
+
</div>
|
|
223
560
|
{/if}
|
|
224
561
|
|
|
225
|
-
{#if volumes[active_volume_idx]}
|
|
562
|
+
{#if !is_multi_layer && volumes[active_volume_idx]}
|
|
226
563
|
<div class="grid-info">
|
|
227
564
|
{volumes[active_volume_idx].grid_dims.join(` × `)} grid | [{format_num(
|
|
228
565
|
data_range.min,
|
|
@@ -233,6 +570,40 @@
|
|
|
233
570
|
</SettingsSection>
|
|
234
571
|
|
|
235
572
|
<style>
|
|
573
|
+
/* Shared chrome for native selects/number/color + ColorScaleSelect (--sms-*) */
|
|
574
|
+
:is(.compact-row, .volume-group, .display-range) {
|
|
575
|
+
--iso-ctrl-h: 22px;
|
|
576
|
+
--iso-ctrl-radius: 3px;
|
|
577
|
+
--iso-ctrl-border: 1px solid var(--border-color, light-dark(#b8bec8, #555));
|
|
578
|
+
--iso-ctrl-bg: light-dark(#fff, #1e1e1e);
|
|
579
|
+
--sms-min-height: var(--iso-ctrl-h);
|
|
580
|
+
--sms-border: var(--iso-ctrl-border);
|
|
581
|
+
--sms-border-radius: var(--iso-ctrl-radius);
|
|
582
|
+
--sms-bg: var(--iso-ctrl-bg);
|
|
583
|
+
font-size: 0.95em;
|
|
584
|
+
|
|
585
|
+
:is(select, input[type='number'], input[type='color']) {
|
|
586
|
+
box-sizing: border-box;
|
|
587
|
+
margin: 0; /* beat DraggablePane margins */
|
|
588
|
+
border: var(--iso-ctrl-border);
|
|
589
|
+
border-radius: var(--iso-ctrl-radius);
|
|
590
|
+
}
|
|
591
|
+
:is(select, input[type='number']) {
|
|
592
|
+
height: var(--iso-ctrl-h);
|
|
593
|
+
padding: 0 4px;
|
|
594
|
+
background: var(--iso-ctrl-bg);
|
|
595
|
+
color: inherit;
|
|
596
|
+
font: inherit;
|
|
597
|
+
line-height: 1.2;
|
|
598
|
+
}
|
|
599
|
+
input[type='color'] {
|
|
600
|
+
width: var(--iso-ctrl-h);
|
|
601
|
+
height: var(--iso-ctrl-h);
|
|
602
|
+
padding: 0;
|
|
603
|
+
cursor: pointer;
|
|
604
|
+
background: transparent;
|
|
605
|
+
}
|
|
606
|
+
}
|
|
236
607
|
.compact-row {
|
|
237
608
|
flex-wrap: wrap;
|
|
238
609
|
gap: 4pt 14pt;
|
|
@@ -245,28 +616,136 @@
|
|
|
245
616
|
opacity: 0.7;
|
|
246
617
|
padding: 2px 0;
|
|
247
618
|
}
|
|
248
|
-
.
|
|
619
|
+
.volume-group {
|
|
620
|
+
display: flex;
|
|
621
|
+
flex-direction: column;
|
|
622
|
+
gap: 3px;
|
|
623
|
+
min-width: 0;
|
|
624
|
+
padding: 3px 0;
|
|
625
|
+
border-top: 1px solid light-dark(rgba(0, 0, 0, 0.1), rgba(255, 255, 255, 0.12));
|
|
626
|
+
}
|
|
627
|
+
.volume-header {
|
|
628
|
+
display: flex;
|
|
629
|
+
flex-wrap: wrap;
|
|
630
|
+
align-items: center;
|
|
631
|
+
gap: 0.4em;
|
|
632
|
+
min-width: 0;
|
|
633
|
+
}
|
|
634
|
+
.volume-label {
|
|
635
|
+
font-weight: 600;
|
|
636
|
+
overflow: hidden;
|
|
637
|
+
text-overflow: ellipsis;
|
|
638
|
+
white-space: nowrap;
|
|
639
|
+
min-width: 0;
|
|
640
|
+
max-width: 14em;
|
|
641
|
+
}
|
|
642
|
+
.volume-dims,
|
|
643
|
+
.volume-note {
|
|
644
|
+
opacity: 0.6;
|
|
645
|
+
white-space: nowrap;
|
|
646
|
+
}
|
|
647
|
+
.volume-header .icon-btn:first-of-type {
|
|
648
|
+
margin-left: auto;
|
|
649
|
+
}
|
|
650
|
+
.icon-btn {
|
|
651
|
+
display: inline-grid;
|
|
652
|
+
place-items: center;
|
|
653
|
+
width: var(--iso-ctrl-h);
|
|
654
|
+
height: var(--iso-ctrl-h);
|
|
655
|
+
padding: 0;
|
|
656
|
+
border: none;
|
|
657
|
+
border-radius: var(--iso-ctrl-radius);
|
|
658
|
+
background: transparent;
|
|
659
|
+
color: inherit;
|
|
660
|
+
font-size: 0.875rem;
|
|
661
|
+
line-height: 1;
|
|
662
|
+
opacity: 0.7;
|
|
663
|
+
cursor: pointer;
|
|
664
|
+
}
|
|
665
|
+
.icon-btn:hover {
|
|
666
|
+
opacity: 1;
|
|
667
|
+
background: light-dark(rgba(0, 0, 0, 0.08), rgba(255, 255, 255, 0.15));
|
|
668
|
+
}
|
|
669
|
+
.layer-row,
|
|
670
|
+
.color-row {
|
|
249
671
|
display: flex;
|
|
672
|
+
flex-wrap: wrap;
|
|
250
673
|
align-items: center;
|
|
251
674
|
gap: 0.3em;
|
|
252
|
-
|
|
253
|
-
input[type='color'] {
|
|
254
|
-
width: 24px;
|
|
255
|
-
height: 20px;
|
|
256
|
-
padding: 0;
|
|
257
|
-
border: 1px solid var(--border-color, #ccc);
|
|
258
|
-
border-radius: 3px;
|
|
259
|
-
box-sizing: border-box;
|
|
260
|
-
cursor: pointer;
|
|
261
|
-
}
|
|
675
|
+
min-width: 0;
|
|
262
676
|
input[type='checkbox'] {
|
|
263
677
|
margin: 0;
|
|
678
|
+
flex-shrink: 0;
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
.layer-row {
|
|
682
|
+
.slider-field {
|
|
683
|
+
display: inline-flex;
|
|
684
|
+
align-items: center;
|
|
685
|
+
gap: 2px;
|
|
686
|
+
min-width: 0;
|
|
687
|
+
}
|
|
688
|
+
.slider-field + .slider-field {
|
|
689
|
+
margin-inline-start: 0.7em;
|
|
690
|
+
}
|
|
691
|
+
.slider-field:has(.isovalue-slider) {
|
|
692
|
+
flex: 1 1 6em;
|
|
693
|
+
}
|
|
694
|
+
.isovalue-slider {
|
|
695
|
+
flex: 1 1 4em;
|
|
696
|
+
min-width: 3em;
|
|
697
|
+
margin: 0;
|
|
698
|
+
}
|
|
699
|
+
.opacity-slider {
|
|
700
|
+
width: 40px;
|
|
701
|
+
min-width: 0;
|
|
702
|
+
margin: 0;
|
|
703
|
+
}
|
|
704
|
+
.layer-value {
|
|
705
|
+
font-family: monospace;
|
|
706
|
+
font-variant-numeric: tabular-nums;
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
.color-row {
|
|
710
|
+
padding-left: 1.4em;
|
|
711
|
+
label {
|
|
712
|
+
display: flex;
|
|
713
|
+
align-items: center;
|
|
714
|
+
gap: 4pt;
|
|
715
|
+
min-width: 0;
|
|
716
|
+
}
|
|
717
|
+
select {
|
|
718
|
+
max-width: min(9em, 100%);
|
|
719
|
+
}
|
|
720
|
+
:global(.multiselect) {
|
|
721
|
+
flex: 1 1 10em;
|
|
722
|
+
min-width: 0;
|
|
723
|
+
font: inherit;
|
|
724
|
+
color: inherit;
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
.range-input {
|
|
728
|
+
width: 4.5em;
|
|
729
|
+
}
|
|
730
|
+
.color-range {
|
|
731
|
+
display: flex;
|
|
732
|
+
flex-wrap: wrap;
|
|
733
|
+
align-items: center;
|
|
734
|
+
gap: 3px;
|
|
735
|
+
}
|
|
736
|
+
.display-range {
|
|
737
|
+
gap: 4pt 8pt;
|
|
738
|
+
.range-axis {
|
|
739
|
+
display: flex;
|
|
740
|
+
align-items: center;
|
|
741
|
+
gap: 3pt;
|
|
742
|
+
input {
|
|
743
|
+
width: 3.8em;
|
|
744
|
+
}
|
|
264
745
|
}
|
|
265
746
|
}
|
|
266
|
-
.
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
min-width: 3.5em;
|
|
270
|
-
text-align: right;
|
|
747
|
+
.compat-warning {
|
|
748
|
+
cursor: help;
|
|
749
|
+
color: light-dark(#b45309, #fbbf24);
|
|
271
750
|
}
|
|
272
751
|
</style>
|