matterviz 0.3.2 → 0.3.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/EmptyState.svelte +10 -2
- package/dist/FilePicker.svelte +123 -82
- package/dist/Icon.svelte +18 -12
- package/dist/MillerIndexInput.svelte +27 -21
- package/dist/api/optimade.js +6 -6
- package/dist/app.css +216 -207
- package/dist/brillouin/BrillouinZone.svelte +292 -149
- package/dist/brillouin/BrillouinZone.svelte.d.ts +1 -1
- package/dist/brillouin/BrillouinZoneControls.svelte +32 -5
- package/dist/brillouin/BrillouinZoneExportPane.svelte +69 -42
- package/dist/brillouin/BrillouinZoneExportPane.svelte.d.ts +1 -1
- package/dist/brillouin/BrillouinZoneInfoPane.svelte +99 -68
- package/dist/brillouin/BrillouinZoneScene.svelte +275 -163
- package/dist/brillouin/BrillouinZoneScene.svelte.d.ts +1 -1
- package/dist/brillouin/BrillouinZoneTooltip.svelte +17 -7
- package/dist/brillouin/compute.js +11 -6
- package/dist/chempot-diagram/ChemPotDiagram.svelte +162 -27
- package/dist/chempot-diagram/ChemPotDiagram2D.svelte +451 -281
- package/dist/chempot-diagram/ChemPotDiagram3D.svelte +2148 -1642
- package/dist/chempot-diagram/ChemPotScene3D.svelte +8 -5
- package/dist/chempot-diagram/async-compute.svelte.d.ts +3 -0
- package/dist/chempot-diagram/async-compute.svelte.js +77 -0
- package/dist/chempot-diagram/chempot-worker.d.ts +1 -0
- package/dist/chempot-diagram/chempot-worker.js +11 -0
- package/dist/chempot-diagram/color.js +1 -2
- package/dist/chempot-diagram/compute.d.ts +10 -0
- package/dist/chempot-diagram/compute.js +250 -88
- package/dist/chempot-diagram/index.d.ts +2 -1
- package/dist/chempot-diagram/index.js +2 -1
- package/dist/chempot-diagram/temperature.js +8 -9
- package/dist/chempot-diagram/types.d.ts +3 -0
- package/dist/chempot-diagram/types.js +1 -0
- package/dist/colors/index.d.ts +1 -1
- package/dist/colors/index.js +5 -3
- package/dist/composition/BarChart.svelte +128 -55
- package/dist/composition/BubbleChart.svelte +102 -49
- package/dist/composition/Composition.svelte +100 -79
- package/dist/composition/Formula.svelte +108 -62
- package/dist/composition/FormulaFilter.svelte +665 -537
- package/dist/composition/PieChart.svelte +183 -108
- package/dist/composition/format.d.ts +5 -0
- package/dist/composition/format.js +20 -3
- package/dist/composition/parse.js +14 -9
- package/dist/convex-hull/ConvexHull.svelte +93 -40
- package/dist/convex-hull/ConvexHull.svelte.d.ts +1 -1
- package/dist/convex-hull/ConvexHull2D.svelte +549 -360
- package/dist/convex-hull/ConvexHull2D.svelte.d.ts +1 -1
- package/dist/convex-hull/ConvexHull3D.svelte +1296 -827
- package/dist/convex-hull/ConvexHull3D.svelte.d.ts +1 -1
- package/dist/convex-hull/ConvexHull4D.svelte +1004 -688
- package/dist/convex-hull/ConvexHull4D.svelte.d.ts +1 -1
- package/dist/convex-hull/ConvexHullControls.svelte +115 -28
- package/dist/convex-hull/ConvexHullControls.svelte.d.ts +1 -1
- package/dist/convex-hull/ConvexHullInfoPane.svelte +29 -3
- package/dist/convex-hull/ConvexHullStats.svelte +425 -328
- package/dist/convex-hull/ConvexHullTooltip.svelte +40 -16
- package/dist/convex-hull/GasPressureControls.svelte +104 -61
- package/dist/convex-hull/StructurePopup.svelte +25 -4
- package/dist/convex-hull/TemperatureSlider.svelte +45 -25
- package/dist/convex-hull/barycentric-coords.js +13 -7
- package/dist/convex-hull/demo-temperature.js +8 -4
- package/dist/convex-hull/gas-thermodynamics.js +17 -12
- package/dist/convex-hull/helpers.d.ts +9 -0
- package/dist/convex-hull/helpers.js +77 -34
- package/dist/convex-hull/thermodynamics.js +61 -56
- package/dist/convex-hull/types.d.ts +9 -14
- package/dist/convex-hull/types.js +0 -17
- package/dist/coordination/CoordinationBarPlot.svelte +227 -154
- package/dist/element/BohrAtom.svelte +55 -12
- package/dist/element/ElementHeading.svelte +7 -2
- package/dist/element/ElementPhoto.svelte +15 -9
- package/dist/element/ElementStats.svelte +10 -4
- package/dist/element/ElementTile.svelte +137 -73
- package/dist/element/Nucleus.svelte +39 -11
- package/dist/feedback/ClickFeedback.svelte +16 -5
- package/dist/feedback/DragOverlay.svelte +10 -2
- package/dist/feedback/Spinner.svelte +4 -2
- package/dist/feedback/StatusMessage.svelte +8 -2
- package/dist/fermi-surface/FermiSlice.svelte +118 -88
- package/dist/fermi-surface/FermiSurface.svelte +328 -187
- package/dist/fermi-surface/FermiSurface.svelte.d.ts +1 -1
- package/dist/fermi-surface/FermiSurfaceControls.svelte +113 -46
- package/dist/fermi-surface/FermiSurfaceControls.svelte.d.ts +1 -1
- package/dist/fermi-surface/FermiSurfaceScene.svelte +535 -342
- package/dist/fermi-surface/FermiSurfaceScene.svelte.d.ts +1 -1
- package/dist/fermi-surface/FermiSurfaceTooltip.svelte +14 -5
- package/dist/fermi-surface/compute.js +16 -20
- package/dist/fermi-surface/parse.js +24 -14
- package/dist/fermi-surface/symmetry.js +2 -7
- package/dist/fermi-surface/types.d.ts +3 -5
- package/dist/heatmap-matrix/HeatmapMatrix.svelte +1019 -765
- package/dist/heatmap-matrix/HeatmapMatrix.svelte.d.ts +1 -1
- package/dist/heatmap-matrix/HeatmapMatrixControls.svelte +76 -22
- package/dist/heatmap-matrix/HeatmapMatrixControls.svelte.d.ts +2 -3
- package/dist/icons.js +47 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/io/decompress.js +1 -1
- package/dist/io/export.d.ts +3 -0
- package/dist/io/export.js +129 -143
- package/dist/io/is-binary.js +2 -3
- package/dist/io/url-drop.js +1 -2
- package/dist/isosurface/Isosurface.svelte +202 -148
- package/dist/isosurface/IsosurfaceControls.svelte +46 -28
- package/dist/isosurface/parse.js +34 -29
- package/dist/isosurface/slice.js +5 -10
- package/dist/isosurface/types.d.ts +2 -1
- package/dist/isosurface/types.js +61 -12
- package/dist/labels.js +11 -8
- package/dist/layout/FullscreenToggle.svelte +11 -2
- package/dist/layout/InfoCard.svelte +38 -6
- package/dist/layout/InfoTag.svelte +63 -32
- package/dist/layout/PropertyFilter.svelte +82 -37
- package/dist/layout/SettingsSection.svelte +85 -55
- package/dist/layout/SubpageGrid.svelte +10 -2
- package/dist/layout/json-tree/JsonNode.svelte +183 -138
- package/dist/layout/json-tree/JsonTree.svelte +499 -413
- package/dist/layout/json-tree/JsonValue.svelte +127 -99
- package/dist/layout/json-tree/utils.js +4 -2
- package/dist/marching-cubes.js +25 -2
- package/dist/math.d.ts +13 -17
- package/dist/math.js +133 -67
- package/dist/overlays/ContextMenu.svelte +65 -40
- package/dist/overlays/DraggablePane.svelte +211 -139
- package/dist/periodic-table/PeriodicTable.svelte +278 -145
- package/dist/periodic-table/PeriodicTableControls.svelte +178 -128
- package/dist/periodic-table/PropertySelect.svelte +25 -7
- package/dist/periodic-table/TableInset.svelte +8 -3
- package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte +446 -309
- package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte.d.ts +1 -1
- package/dist/phase-diagram/PhaseDiagramControls.svelte +102 -43
- package/dist/phase-diagram/PhaseDiagramControls.svelte.d.ts +1 -1
- package/dist/phase-diagram/PhaseDiagramEditorPane.svelte +63 -40
- package/dist/phase-diagram/PhaseDiagramExportPane.svelte +71 -28
- package/dist/phase-diagram/PhaseDiagramExportPane.svelte.d.ts +1 -1
- package/dist/phase-diagram/PhaseDiagramTooltip.svelte +158 -101
- package/dist/phase-diagram/TdbInfoPanel.svelte +28 -4
- package/dist/phase-diagram/build-diagram.js +9 -9
- package/dist/phase-diagram/colors.js +1 -3
- package/dist/phase-diagram/parse.js +10 -9
- package/dist/phase-diagram/svg-to-diagram.js +53 -49
- package/dist/phase-diagram/utils.d.ts +1 -0
- package/dist/phase-diagram/utils.js +80 -25
- package/dist/plot/AxisLabel.svelte +28 -3
- package/dist/plot/BarPlot.svelte +1182 -734
- package/dist/plot/BarPlot.svelte.d.ts +2 -2
- package/dist/plot/BarPlotControls.svelte +31 -5
- package/dist/plot/BarPlotControls.svelte.d.ts +1 -1
- package/dist/plot/ColorBar.svelte +479 -329
- package/dist/plot/ColorScaleSelect.svelte +27 -6
- package/dist/plot/ElementScatter.svelte +36 -15
- package/dist/plot/FillArea.svelte +152 -95
- package/dist/plot/Histogram.svelte +934 -571
- package/dist/plot/Histogram.svelte.d.ts +1 -1
- package/dist/plot/HistogramControls.svelte +53 -9
- package/dist/plot/HistogramControls.svelte.d.ts +1 -1
- package/dist/plot/InteractiveAxisLabel.svelte +34 -11
- package/dist/plot/InteractiveAxisLabel.svelte.d.ts +1 -1
- package/dist/plot/Line.svelte +63 -28
- package/dist/plot/PlotControls.svelte +157 -114
- package/dist/plot/PlotControls.svelte.d.ts +1 -1
- package/dist/plot/PlotLegend.svelte +174 -91
- package/dist/plot/PlotTooltip.svelte +45 -6
- package/dist/plot/PortalSelect.svelte +175 -147
- package/dist/plot/ReferenceLine.svelte +76 -22
- package/dist/plot/ReferenceLine3D.svelte +132 -107
- package/dist/plot/ReferencePlane.svelte +146 -121
- package/dist/plot/ScatterPlot.svelte +1681 -1091
- package/dist/plot/ScatterPlot.svelte.d.ts +2 -2
- package/dist/plot/ScatterPlot3D.svelte +256 -131
- package/dist/plot/ScatterPlot3D.svelte.d.ts +2 -2
- package/dist/plot/ScatterPlot3DControls.svelte +113 -63
- package/dist/plot/ScatterPlot3DControls.svelte.d.ts +2 -1
- package/dist/plot/ScatterPlot3DScene.svelte +608 -403
- package/dist/plot/ScatterPlot3DScene.svelte.d.ts +2 -2
- package/dist/plot/ScatterPlotControls.svelte +65 -25
- package/dist/plot/ScatterPlotControls.svelte.d.ts +1 -1
- package/dist/plot/ScatterPoint.svelte +98 -26
- package/dist/plot/ScatterPoint.svelte.d.ts +1 -0
- package/dist/plot/SpacegroupBarPlot.svelte +142 -85
- package/dist/plot/Surface3D.svelte +159 -108
- package/dist/plot/ZeroLines.svelte +55 -3
- package/dist/plot/ZoomRect.svelte +4 -2
- package/dist/plot/axis-utils.js +1 -3
- package/dist/plot/data-cleaning.js +12 -28
- package/dist/plot/data-transform.js +2 -1
- package/dist/plot/fill-utils.js +2 -0
- package/dist/plot/layout.d.ts +4 -1
- package/dist/plot/layout.js +33 -14
- package/dist/plot/reference-line.d.ts +2 -2
- package/dist/plot/reference-line.js +7 -5
- package/dist/plot/scales.js +24 -36
- package/dist/plot/types.d.ts +11 -23
- package/dist/plot/types.js +6 -11
- package/dist/plot/utils/label-placement.d.ts +32 -15
- package/dist/plot/utils/label-placement.js +227 -66
- package/dist/plot/utils/series-visibility.js +2 -3
- package/dist/rdf/RdfPlot.svelte +143 -91
- package/dist/rdf/calc-rdf.js +4 -5
- package/dist/sanitize.d.ts +4 -0
- package/dist/sanitize.js +107 -0
- package/dist/settings.d.ts +18 -6
- package/dist/settings.js +46 -16
- package/dist/spectral/Bands.svelte +632 -453
- package/dist/spectral/BandsAndDos.svelte +90 -49
- package/dist/spectral/BrillouinBandsDos.svelte +151 -93
- package/dist/spectral/Dos.svelte +389 -258
- package/dist/spectral/helpers.js +55 -43
- package/dist/state.svelte.d.ts +1 -1
- package/dist/state.svelte.js +3 -2
- package/dist/structure/Arrow.svelte +59 -20
- package/dist/structure/AtomLegend.svelte +215 -134
- package/dist/structure/Bond.svelte +73 -47
- package/dist/structure/CanvasTooltip.svelte +10 -2
- package/dist/structure/CellSelect.svelte +72 -45
- package/dist/structure/Cylinder.svelte +33 -17
- package/dist/structure/Lattice.svelte +88 -33
- package/dist/structure/Structure.svelte +1063 -797
- package/dist/structure/Structure.svelte.d.ts +1 -1
- package/dist/structure/StructureControls.svelte +349 -118
- package/dist/structure/StructureExportPane.svelte +124 -89
- package/dist/structure/StructureExportPane.svelte.d.ts +1 -1
- package/dist/structure/StructureInfoPane.svelte +304 -237
- package/dist/structure/StructureScene.svelte +879 -443
- package/dist/structure/StructureScene.svelte.d.ts +15 -7
- package/dist/structure/atom-properties.js +8 -8
- package/dist/structure/bonding.js +6 -7
- package/dist/structure/export.js +14 -29
- package/dist/structure/ferrox-wasm.js +1 -1
- package/dist/structure/index.d.ts +13 -3
- package/dist/structure/index.js +83 -23
- package/dist/structure/measure.d.ts +2 -2
- package/dist/structure/measure.js +4 -44
- package/dist/structure/parse.js +113 -141
- package/dist/structure/partial-occupancy.js +7 -10
- package/dist/structure/pbc.d.ts +1 -0
- package/dist/structure/pbc.js +16 -6
- package/dist/structure/supercell.d.ts +2 -2
- package/dist/structure/supercell.js +12 -22
- package/dist/structure/validation.js +1 -2
- package/dist/symmetry/SymmetryStats.svelte +84 -41
- package/dist/symmetry/WyckoffTable.svelte +26 -6
- package/dist/symmetry/cell-transform.js +5 -3
- package/dist/symmetry/index.js +8 -7
- package/dist/symmetry/spacegroups.js +148 -148
- package/dist/table/HeatmapTable.svelte +790 -554
- package/dist/table/HeatmapTable.svelte.d.ts +1 -1
- package/dist/table/ToggleMenu.svelte +125 -92
- package/dist/table/index.js +2 -4
- package/dist/theme/ThemeControl.svelte +21 -12
- package/dist/time.js +4 -1
- package/dist/tooltip/TooltipContent.svelte +33 -8
- package/dist/trajectory/Trajectory.svelte +758 -558
- package/dist/trajectory/TrajectoryError.svelte +14 -3
- package/dist/trajectory/TrajectoryExportPane.svelte +137 -83
- package/dist/trajectory/TrajectoryInfoPane.svelte +272 -143
- package/dist/trajectory/extract.js +10 -26
- package/dist/trajectory/format-detect.js +5 -5
- package/dist/trajectory/frame-reader.d.ts +1 -1
- package/dist/trajectory/frame-reader.js +5 -12
- package/dist/trajectory/helpers.d.ts +0 -1
- package/dist/trajectory/helpers.js +2 -17
- package/dist/trajectory/index.js +14 -12
- package/dist/trajectory/parse/ase.js +5 -4
- package/dist/trajectory/parse/hdf5.js +26 -18
- package/dist/trajectory/parse/index.js +13 -18
- package/dist/trajectory/parse/lammps.js +17 -7
- package/dist/trajectory/parse/vasp.js +5 -2
- package/dist/trajectory/parse/xyz.js +8 -7
- package/dist/trajectory/plotting.js +13 -8
- package/dist/utils.d.ts +1 -0
- package/dist/utils.js +13 -0
- package/dist/xrd/XrdPlot.svelte +337 -247
- package/dist/xrd/broadening.js +14 -9
- package/dist/xrd/calc-xrd.js +12 -18
- package/dist/xrd/parse.d.ts +1 -1
- package/dist/xrd/parse.js +17 -17
- package/package.json +99 -103
- package/readme.md +1 -1
- /package/dist/theme/{themes.js → themes.mjs} +0 -0
|
@@ -1,401 +1,551 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import {
|
|
6
|
-
import
|
|
7
|
-
import * as
|
|
8
|
-
import {
|
|
9
|
-
import
|
|
10
|
-
import
|
|
11
|
-
import {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { luminance } from '../colors'
|
|
3
|
+
import Spinner from '../feedback/Spinner.svelte'
|
|
4
|
+
import { format_num } from '../labels'
|
|
5
|
+
import { sanitize_html } from '../sanitize'
|
|
6
|
+
import type { Vec2 } from '../math'
|
|
7
|
+
import * as math from '../math'
|
|
8
|
+
import { format } from 'd3-format'
|
|
9
|
+
import * as d3 from 'd3-scale'
|
|
10
|
+
import * as d3_sc from 'd3-scale-chromatic'
|
|
11
|
+
import { timeFormat } from 'd3-time-format'
|
|
12
|
+
import type { HTMLAttributes } from 'svelte/elements'
|
|
13
|
+
import type { D3InterpolateName } from '../colors'
|
|
14
|
+
import PortalSelect from './PortalSelect.svelte'
|
|
15
|
+
import { generate_arcsinh_ticks, scale_arcsinh } from './scales'
|
|
16
|
+
import type {
|
|
17
|
+
AxisOption,
|
|
18
|
+
ColorBarDataLoaderFn,
|
|
19
|
+
ColorScaleOption,
|
|
20
|
+
Orientation,
|
|
21
|
+
ScaleType,
|
|
22
|
+
} from './types'
|
|
23
|
+
import { get_arcsinh_threshold, get_scale_type_name } from './types'
|
|
24
|
+
|
|
25
|
+
let {
|
|
26
|
+
title = $bindable(),
|
|
27
|
+
color_scale = $bindable(`interpolateViridis`),
|
|
28
|
+
bar_style = undefined,
|
|
29
|
+
title_style = undefined,
|
|
30
|
+
wrapper_style = undefined,
|
|
31
|
+
tick_labels = $bindable(4),
|
|
32
|
+
tick_format = undefined,
|
|
33
|
+
range = $bindable([0, 1]),
|
|
34
|
+
orientation = `horizontal`,
|
|
35
|
+
snap_ticks = true,
|
|
36
|
+
steps = 50,
|
|
37
|
+
nice_range = $bindable(range),
|
|
38
|
+
title_side = undefined, // no default here, depends on orientation and tick_side
|
|
39
|
+
tick_side = `primary`,
|
|
40
|
+
scale_type = `linear`,
|
|
41
|
+
color_scale_fn = undefined,
|
|
42
|
+
color_scale_domain = undefined,
|
|
43
|
+
// Property selection (interactive title)
|
|
44
|
+
property_options = undefined,
|
|
45
|
+
selected_property_key = $bindable(),
|
|
46
|
+
data_loader = undefined,
|
|
47
|
+
on_property_change = undefined,
|
|
48
|
+
// Color scale selection
|
|
49
|
+
color_scale_options = undefined,
|
|
50
|
+
selected_color_scale_key = $bindable(),
|
|
51
|
+
on_color_scale_change = undefined,
|
|
52
|
+
...rest
|
|
53
|
+
}: HTMLAttributes<HTMLDivElement> & {
|
|
54
|
+
title?: string
|
|
55
|
+
color_scale?: ((x: number) => string) | string | null
|
|
56
|
+
title_side?: `left` | `right` | `top` | `bottom`
|
|
57
|
+
bar_style?: string
|
|
58
|
+
title_style?: string
|
|
59
|
+
wrapper_style?: string
|
|
60
|
+
tick_labels?: (string | number)[] | number
|
|
61
|
+
tick_format?: string
|
|
62
|
+
range?: [number, number]
|
|
63
|
+
// tick_side determines tick placement relative to orientation:
|
|
64
|
+
// 'primary' = bottom (horizontal) / right (vertical), outside bar
|
|
65
|
+
// 'secondary' = top (horizontal) / left (vertical), outside bar
|
|
66
|
+
// 'inside' = centered within bar, hiding first/last
|
|
67
|
+
tick_side?: `primary` | `secondary` | `inside`
|
|
68
|
+
orientation?: Orientation
|
|
69
|
+
// snap ticks to pretty, more readable values
|
|
70
|
+
snap_ticks?: boolean
|
|
71
|
+
// number of equidistant points to sample color scale
|
|
72
|
+
steps?: number
|
|
73
|
+
// computed "nice" range resulting from snapping ticks
|
|
74
|
+
// https://github.com/d3/d3-scale/issues/86
|
|
75
|
+
nice_range?: [number, number]
|
|
76
|
+
// type of scale to use for ticks and potentially color (if color_scale_fn not provided)
|
|
77
|
+
scale_type?: ScaleType
|
|
78
|
+
// Optional pre-configured d3 color scale function
|
|
79
|
+
color_scale_fn?: (value: number) => string
|
|
80
|
+
// Optional domain for pre-configured color scale function
|
|
81
|
+
color_scale_domain?: [number, number]
|
|
82
|
+
// Property selection options (makes title interactive)
|
|
83
|
+
property_options?: AxisOption[]
|
|
84
|
+
selected_property_key?: string
|
|
85
|
+
data_loader?: ColorBarDataLoaderFn
|
|
86
|
+
on_property_change?: (key: string, range: [number, number]) => void
|
|
87
|
+
// Color scale selection options
|
|
88
|
+
color_scale_options?: ColorScaleOption[]
|
|
89
|
+
selected_color_scale_key?: string
|
|
90
|
+
on_color_scale_change?: (key: string) => void
|
|
91
|
+
} = $props()
|
|
92
|
+
|
|
93
|
+
// Loading state for property data fetching
|
|
94
|
+
let loading = $state(false)
|
|
95
|
+
|
|
96
|
+
let actual_title_side = $derived.by(() => {
|
|
97
|
+
if (title_side !== undefined) return title_side // Use user-provided value if available
|
|
98
|
+
|
|
23
99
|
// Calculate default based on orientation and tick_side
|
|
24
|
-
if (tick_side === `inside`)
|
|
25
|
-
|
|
100
|
+
if (tick_side === `inside`) return `left` // Default to left if ticks are inside
|
|
101
|
+
|
|
26
102
|
// If ticks are primary (bottom), default label to top
|
|
27
103
|
// If ticks are secondary (top), default label to bottom
|
|
28
104
|
if (orientation === `horizontal`) {
|
|
29
|
-
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
return tick_side === `primary` ? `left` : `right`;
|
|
105
|
+
return tick_side === `primary` ? `top` : `bottom`
|
|
106
|
+
} else { // orientation === `vertical`
|
|
107
|
+
// If ticks are primary (right), default label to left
|
|
108
|
+
// If ticks are secondary (left), default label to right
|
|
109
|
+
return tick_side === `primary` ? `left` : `right`
|
|
35
110
|
}
|
|
36
|
-
})
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
// Number of ticks to generate
|
|
114
|
+
let n_ticks = $derived(
|
|
115
|
+
Array.isArray(tick_labels)
|
|
116
|
+
? tick_labels.length
|
|
117
|
+
: typeof tick_labels === `number`
|
|
118
|
+
? tick_labels
|
|
119
|
+
: 5,
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
// Scale for ticks - based *only* on 'range' prop and 'scale_type' for ticks
|
|
123
|
+
let scale_for_ticks = $derived.by(() => {
|
|
124
|
+
const type_name = get_scale_type_name(scale_type)
|
|
125
|
+
let use_log_for_ticks = type_name === `log`
|
|
126
|
+
let [scale_min, scale_max] = range
|
|
127
|
+
|
|
48
128
|
// Validate range for log scale ticks and apply epsilon if needed
|
|
49
129
|
if (use_log_for_ticks) {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
130
|
+
if (scale_max <= 0) {
|
|
131
|
+
console.warn(
|
|
132
|
+
`Log scale requires a positive max value for ticks. Received max=${scale_max}. Using linear scale for ticks instead.`,
|
|
133
|
+
)
|
|
134
|
+
use_log_for_ticks = false
|
|
135
|
+
} else if (scale_min <= 0) {
|
|
136
|
+
console.warn(
|
|
137
|
+
`Log scale received non-positive min value (${scale_min}) for ticks. Using epsilon=${math.LOG_EPS} instead.`,
|
|
138
|
+
)
|
|
139
|
+
scale_min = math.LOG_EPS // Substitute with epsilon
|
|
140
|
+
}
|
|
58
141
|
}
|
|
142
|
+
|
|
59
143
|
// For arcsinh, use our custom scale
|
|
60
144
|
if (type_name === `arcsinh`) {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
145
|
+
// Guard against very small thresholds that could cause precision issues
|
|
146
|
+
const threshold = Math.max(get_arcsinh_threshold(scale_type), Number.EPSILON)
|
|
147
|
+
const scale = scale_arcsinh(threshold)
|
|
148
|
+
.domain([scale_min, scale_max])
|
|
149
|
+
.range(orientation === `vertical` ? [100, 0] : [0, 100])
|
|
150
|
+
return scale
|
|
67
151
|
}
|
|
68
|
-
|
|
152
|
+
|
|
153
|
+
const scale = use_log_for_ticks ? d3.scaleLog() : d3.scaleLinear()
|
|
69
154
|
// Use potentially adjusted min/max for domain
|
|
70
|
-
scale.domain([scale_min, scale_max])
|
|
155
|
+
scale.domain([scale_min, scale_max])
|
|
156
|
+
|
|
71
157
|
// Set range based on orientation for positioning (0-100 for percent)
|
|
72
|
-
scale.range(orientation === `vertical` ? [100, 0] : [0, 100])
|
|
158
|
+
scale.range(orientation === `vertical` ? [100, 0] : [0, 100])
|
|
159
|
+
|
|
73
160
|
// Apply scale.nice() only if snapping is enabled and not an explicit array.
|
|
74
161
|
if (snap_ticks && !Array.isArray(tick_labels)) {
|
|
75
|
-
|
|
162
|
+
scale.nice(n_ticks)
|
|
76
163
|
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
164
|
+
|
|
165
|
+
return scale
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
let ticks_array: number[] = $derived.by(() => {
|
|
80
169
|
if (Array.isArray(tick_labels)) {
|
|
81
|
-
|
|
82
|
-
|
|
170
|
+
// Use user-provided ticks directly
|
|
171
|
+
return tick_labels.map(Number).filter((n) => !isNaN(n))
|
|
83
172
|
}
|
|
173
|
+
|
|
84
174
|
// Handle edge cases for number of ticks
|
|
85
|
-
if (n_ticks <= 0)
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
const
|
|
90
|
-
const
|
|
91
|
-
|
|
175
|
+
if (n_ticks <= 0) return []
|
|
176
|
+
if (n_ticks === 1) return [scale_for_ticks.domain()[0]]
|
|
177
|
+
|
|
178
|
+
const scale = scale_for_ticks // Use derived scale (which handles log validation for ticks)
|
|
179
|
+
const [scale_min, scale_max] = scale.domain()
|
|
180
|
+
const type_name = get_scale_type_name(scale_type)
|
|
181
|
+
|
|
92
182
|
// Arcsinh tick generation
|
|
93
183
|
if (type_name === `arcsinh`) {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
184
|
+
// Guard against very small thresholds that could cause precision issues
|
|
185
|
+
const threshold = Math.max(get_arcsinh_threshold(scale_type), Number.EPSILON)
|
|
186
|
+
return generate_arcsinh_ticks(scale_min, scale_max, threshold, n_ticks)
|
|
97
187
|
}
|
|
188
|
+
|
|
98
189
|
// check scale_type prop for log tick generation
|
|
99
|
-
const use_log_ticks = type_name === `log` && scale_min > 0 && scale_max > 0
|
|
190
|
+
const use_log_ticks = type_name === `log` && scale_min > 0 && scale_max > 0
|
|
191
|
+
|
|
100
192
|
if (use_log_ticks) {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
const FRACTIONAL_TOL = 1e-10;
|
|
113
|
-
if (Math.abs(Math.log10(nice_min) % 1) < FRACTIONAL_TOL &&
|
|
114
|
-
!power_of_10_ticks.includes(nice_min))
|
|
115
|
-
power_of_10_ticks.unshift(nice_min);
|
|
116
|
-
if (Math.abs(Math.log10(nice_max) % 1) < FRACTIONAL_TOL &&
|
|
117
|
-
!power_of_10_ticks.includes(nice_max))
|
|
118
|
-
power_of_10_ticks.push(nice_max);
|
|
119
|
-
// If no powers of 10 are within range (e.g. [0.1, 0.9]), fall back to D3 ticks?
|
|
120
|
-
// Or just return filtered list which might be empty?
|
|
121
|
-
// For now, let's stick with only powers of 10.
|
|
122
|
-
// If list is empty maybe return domain ends?
|
|
123
|
-
if (power_of_10_ticks.length === 0) {
|
|
124
|
-
// If domain is very small, e.g. [1e-9, 1e-8], no powers of 10.
|
|
125
|
-
// Return exact domain ends as ticks in this edge case.
|
|
126
|
-
return [nice_min, nice_max];
|
|
127
|
-
}
|
|
128
|
-
return power_of_10_ticks;
|
|
129
|
-
}
|
|
130
|
-
else {
|
|
131
|
-
// Generate exactly n_ticks manually for log scale if not snapping
|
|
132
|
-
const log_min = Math.log10(scale_min);
|
|
133
|
-
const log_max = Math.log10(scale_max);
|
|
134
|
-
return [...Array(n_ticks).keys()].map((idx) => {
|
|
135
|
-
const t = idx / (n_ticks - 1);
|
|
136
|
-
const log_val = log_min + t * (log_max - log_min);
|
|
137
|
-
return Math.pow(10, log_val);
|
|
138
|
-
});
|
|
193
|
+
// Use D3's ticks for log scale if snapping is enabled
|
|
194
|
+
if (snap_ticks) {
|
|
195
|
+
// For snapped log ticks, manually generate integer powers of 10 within niced domain.
|
|
196
|
+
const [nice_min, nice_max] = scale.domain()
|
|
197
|
+
|
|
198
|
+
const start_exp = Math.ceil(Math.log10(nice_min))
|
|
199
|
+
const end_exp = Math.floor(Math.log10(nice_max))
|
|
200
|
+
|
|
201
|
+
const power_of_10_ticks: number[] = []
|
|
202
|
+
for (let exp = start_exp; exp <= end_exp; exp++) {
|
|
203
|
+
power_of_10_ticks.push(Math.pow(10, exp))
|
|
139
204
|
}
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
if (
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
205
|
+
|
|
206
|
+
// Ensure domain endpoints are included if they are powers of 10 and missed by loop
|
|
207
|
+
const FRACTIONAL_TOL = 1e-10
|
|
208
|
+
if (
|
|
209
|
+
Math.abs(Math.log10(nice_min) % 1) < FRACTIONAL_TOL &&
|
|
210
|
+
!power_of_10_ticks.includes(nice_min)
|
|
211
|
+
) power_of_10_ticks.unshift(nice_min)
|
|
212
|
+
if (
|
|
213
|
+
Math.abs(Math.log10(nice_max) % 1) < FRACTIONAL_TOL &&
|
|
214
|
+
!power_of_10_ticks.includes(nice_max)
|
|
215
|
+
) power_of_10_ticks.push(nice_max)
|
|
216
|
+
|
|
217
|
+
// If no powers of 10 are within range (e.g. [0.1, 0.9]), fall back to D3 ticks?
|
|
218
|
+
// Or just return filtered list which might be empty?
|
|
219
|
+
// For now, let's stick with only powers of 10.
|
|
220
|
+
// If list is empty maybe return domain ends?
|
|
221
|
+
if (power_of_10_ticks.length === 0) {
|
|
222
|
+
// If domain is very small, e.g. [1e-9, 1e-8], no powers of 10.
|
|
223
|
+
// Return exact domain ends as ticks in this edge case.
|
|
224
|
+
return [nice_min, nice_max]
|
|
151
225
|
}
|
|
226
|
+
|
|
227
|
+
return power_of_10_ticks
|
|
228
|
+
} else {
|
|
229
|
+
// Generate exactly n_ticks manually for log scale if not snapping
|
|
230
|
+
const log_min = Math.log10(scale_min)
|
|
231
|
+
const log_max = Math.log10(scale_max)
|
|
232
|
+
return [...Array(n_ticks).keys()].map((idx) => {
|
|
233
|
+
const t = idx / (n_ticks - 1)
|
|
234
|
+
const log_val = log_min + t * (log_max - log_min)
|
|
235
|
+
return Math.pow(10, log_val)
|
|
236
|
+
})
|
|
237
|
+
}
|
|
238
|
+
} else {
|
|
239
|
+
// Use D3's default nice ticks for linear scale
|
|
240
|
+
if (snap_ticks) return scale.ticks(n_ticks)
|
|
241
|
+
else {
|
|
242
|
+
// Generate exactly n_ticks evenly spaced linear ticks
|
|
243
|
+
return [...Array(n_ticks).keys()].map((idx) => {
|
|
244
|
+
const t = idx / (n_ticks - 1)
|
|
245
|
+
return scale_min + t * (scale_max - scale_min)
|
|
246
|
+
})
|
|
247
|
+
}
|
|
152
248
|
}
|
|
153
|
-
})
|
|
154
|
-
|
|
155
|
-
|
|
249
|
+
})
|
|
250
|
+
|
|
251
|
+
// Update nice_range binding when snapping ticks
|
|
252
|
+
$effect.pre(() => {
|
|
156
253
|
if (snap_ticks && !Array.isArray(tick_labels)) {
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
let actual_color_scale_fn = $derived.by(() => {
|
|
170
|
-
if (color_scale_fn)
|
|
171
|
-
return color_scale_fn; // Prioritize passed function
|
|
254
|
+
// Use derived scale to get niced domain
|
|
255
|
+
const domain = scale_for_ticks.domain()
|
|
256
|
+
// Ensure domain has two elements before assigning
|
|
257
|
+
if (domain.length === 2) nice_range = domain as Vec2
|
|
258
|
+
else nice_range = range // Fallback
|
|
259
|
+
} else nice_range = range // Use original range if not snapping or labels provided
|
|
260
|
+
})
|
|
261
|
+
|
|
262
|
+
// Determine effective color scale function to use
|
|
263
|
+
let actual_color_scale_fn = $derived.by(() => {
|
|
264
|
+
if (color_scale_fn) return color_scale_fn // Prioritize passed function
|
|
265
|
+
|
|
172
266
|
// Fallback: create function from scheme name/function in 'color_scale' prop
|
|
173
|
-
let interpolator = d3_sc.interpolateViridis
|
|
267
|
+
let interpolator = d3_sc.interpolateViridis // Default interpolator
|
|
174
268
|
if (typeof color_scale === `string`) {
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
else if (typeof color_scale === `function`) {
|
|
186
|
-
|
|
187
|
-
|
|
269
|
+
const func_name = color_scale.startsWith(`interpolate`)
|
|
270
|
+
? color_scale
|
|
271
|
+
: `interpolate${color_scale}`
|
|
272
|
+
if (func_name in d3_sc) {
|
|
273
|
+
interpolator = d3_sc[func_name as D3InterpolateName]
|
|
274
|
+
} else {
|
|
275
|
+
console.error(
|
|
276
|
+
`Color scale '${color_scale}' not found. Falling back on 'Viridis'.`,
|
|
277
|
+
)
|
|
278
|
+
}
|
|
279
|
+
} else if (typeof color_scale === `function`) {
|
|
280
|
+
// User passed a function (assumed interpolator [0,1] -> color)
|
|
281
|
+
interpolator = color_scale
|
|
188
282
|
}
|
|
283
|
+
|
|
189
284
|
// Need a domain for this fallback scale! Use 'range' prop.
|
|
190
|
-
let [min_val, max_val] = range
|
|
191
|
-
const type_name = get_scale_type_name(scale_type)
|
|
285
|
+
let [min_val, max_val] = range
|
|
286
|
+
const type_name = get_scale_type_name(scale_type)
|
|
287
|
+
|
|
192
288
|
// Use scale_type for fallback scale creation too. Validate domain for log.
|
|
193
|
-
let use_log_fallback = type_name === `log
|
|
289
|
+
let use_log_fallback = type_name === `log`
|
|
194
290
|
if (use_log_fallback) {
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
291
|
+
if (max_val <= 0) {
|
|
292
|
+
console.warn(
|
|
293
|
+
`Log scale requires a positive max value for fallback scale. Received max=${max_val}. Using linear scale for colors.`,
|
|
294
|
+
)
|
|
295
|
+
use_log_fallback = false
|
|
296
|
+
} else if (min_val <= 0) {
|
|
297
|
+
console.warn(
|
|
298
|
+
`Log scale received non-positive min value (${min_val}) for fallback scale. Using epsilon=${math.LOG_EPS} instead.`,
|
|
299
|
+
)
|
|
300
|
+
min_val = math.LOG_EPS // Substitute with epsilon
|
|
301
|
+
}
|
|
203
302
|
}
|
|
303
|
+
|
|
204
304
|
// Use potentially adjusted min/max for domain (ascending)
|
|
205
|
-
const lo = Math.min(min_val, max_val)
|
|
206
|
-
const hi = Math.max(min_val, max_val)
|
|
207
|
-
const domain_for_scale = [lo, hi]
|
|
305
|
+
const lo = Math.min(min_val, max_val)
|
|
306
|
+
const hi = Math.max(min_val, max_val)
|
|
307
|
+
const domain_for_scale: [number, number] = [lo, hi]
|
|
308
|
+
|
|
208
309
|
// For arcsinh, create a custom color scale
|
|
209
310
|
if (type_name === `arcsinh`) {
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
311
|
+
// Guard against very small thresholds that could cause precision issues
|
|
312
|
+
const threshold = Math.max(get_arcsinh_threshold(scale_type), Number.EPSILON)
|
|
313
|
+
const t_min = Math.asinh(lo / threshold)
|
|
314
|
+
const t_max = Math.asinh(hi / threshold)
|
|
315
|
+
return (value: number): string => {
|
|
316
|
+
const t_val = Math.asinh(value / threshold)
|
|
317
|
+
const normalized = t_max === t_min ? 0.5 : (t_val - t_min) / (t_max - t_min)
|
|
318
|
+
return interpolator(Math.max(0, Math.min(1, normalized)))
|
|
319
|
+
}
|
|
219
320
|
}
|
|
321
|
+
|
|
220
322
|
return use_log_fallback
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
})
|
|
224
|
-
|
|
225
|
-
//
|
|
226
|
-
|
|
227
|
-
let
|
|
228
|
-
|
|
229
|
-
let
|
|
230
|
-
|
|
231
|
-
|
|
323
|
+
? d3.scaleSequentialLog(interpolator).domain(domain_for_scale)
|
|
324
|
+
: d3.scaleSequential(interpolator).domain(domain_for_scale)
|
|
325
|
+
})
|
|
326
|
+
|
|
327
|
+
// Determine effective domain for color ramp interpolation *steps*
|
|
328
|
+
// Prioritize color_scale_domain if provided, otherwise use general 'range' prop.
|
|
329
|
+
let color_interp_domain = $derived(color_scale_domain ?? range)
|
|
330
|
+
|
|
331
|
+
let grad_dir = $derived(orientation === `horizontal` ? `to right` : `to top`)
|
|
332
|
+
|
|
333
|
+
// Generate color stops for gradient background using effective scale and domain
|
|
334
|
+
let ramped = $derived.by(() => {
|
|
335
|
+
const [min_ramp_domain, max_ramp_domain] = color_interp_domain
|
|
336
|
+
const type_name = get_scale_type_name(scale_type)
|
|
337
|
+
|
|
232
338
|
// Validate domain for log interpolation and apply epsilon if needed
|
|
233
|
-
let use_log_interp = type_name === `log
|
|
234
|
-
let adjusted_min_ramp = min_ramp_domain
|
|
235
|
-
let adjusted_max_ramp = max_ramp_domain
|
|
339
|
+
let use_log_interp = type_name === `log`
|
|
340
|
+
let adjusted_min_ramp = min_ramp_domain
|
|
341
|
+
let adjusted_max_ramp = max_ramp_domain
|
|
342
|
+
|
|
236
343
|
if (use_log_interp) {
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
344
|
+
if (max_ramp_domain <= 0) {
|
|
345
|
+
console.warn(
|
|
346
|
+
`Log scale specified for gradient, but max domain value (${max_ramp_domain}) is not positive. Using linear interpolation.`,
|
|
347
|
+
)
|
|
348
|
+
use_log_interp = false
|
|
349
|
+
} else if (min_ramp_domain <= 0) {
|
|
350
|
+
console.warn(
|
|
351
|
+
`Log scale specified for gradient, but min domain value (${min_ramp_domain}) is not positive. Using epsilon=${math.LOG_EPS} instead.`,
|
|
352
|
+
)
|
|
353
|
+
adjusted_min_ramp = math.LOG_EPS // Substitute with epsilon
|
|
354
|
+
}
|
|
245
355
|
}
|
|
246
|
-
|
|
356
|
+
|
|
357
|
+
const n_steps = Math.max(2, Math.floor(steps)) // guard against steps <= 1 to avoid NaN/degenerate gradients
|
|
358
|
+
|
|
247
359
|
// Pre-compute loop-invariant values for each scale type
|
|
248
|
-
let log_min = 0, log_max = 0, log_span = 0
|
|
249
|
-
let asinh_threshold = 1, asinh_min = 0, asinh_max = 0, asinh_span = 0
|
|
250
|
-
const linear_span = max_ramp_domain - min_ramp_domain
|
|
360
|
+
let log_min = 0, log_max = 0, log_span = 0
|
|
361
|
+
let asinh_threshold = 1, asinh_min = 0, asinh_max = 0, asinh_span = 0
|
|
362
|
+
const linear_span = max_ramp_domain - min_ramp_domain
|
|
363
|
+
|
|
251
364
|
if (use_log_interp) {
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
asinh_span = asinh_max - asinh_min;
|
|
365
|
+
log_min = Math.log10(adjusted_min_ramp)
|
|
366
|
+
log_max = Math.log10(adjusted_max_ramp)
|
|
367
|
+
log_span = log_max - log_min
|
|
368
|
+
} else if (type_name === `arcsinh`) {
|
|
369
|
+
// Guard against very small thresholds that could cause precision issues
|
|
370
|
+
asinh_threshold = Math.max(get_arcsinh_threshold(scale_type), Number.EPSILON)
|
|
371
|
+
asinh_min = Math.asinh(min_ramp_domain / asinh_threshold)
|
|
372
|
+
asinh_max = Math.asinh(max_ramp_domain / asinh_threshold)
|
|
373
|
+
asinh_span = asinh_max - asinh_min
|
|
262
374
|
}
|
|
375
|
+
|
|
263
376
|
return [...Array(n_steps).keys()].map((_, idx) => {
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
// Determine wrapper flex-direction based on actual title_side
|
|
283
|
-
let wrapper_flex_dir = $derived(
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
377
|
+
const t = idx / (n_steps - 1) // Normalized position 0 to 1
|
|
378
|
+
let data_value: number
|
|
379
|
+
|
|
380
|
+
if (use_log_interp) {
|
|
381
|
+
data_value = log_span === 0
|
|
382
|
+
? adjusted_min_ramp
|
|
383
|
+
: Math.pow(10, log_min + t * log_span)
|
|
384
|
+
} else if (type_name === `arcsinh`) {
|
|
385
|
+
data_value = asinh_span === 0
|
|
386
|
+
? min_ramp_domain
|
|
387
|
+
: Math.sinh(asinh_min + t * asinh_span) * asinh_threshold
|
|
388
|
+
} else {
|
|
389
|
+
data_value = min_ramp_domain + t * linear_span
|
|
390
|
+
}
|
|
391
|
+
return actual_color_scale_fn(data_value) ?? `transparent`
|
|
392
|
+
})
|
|
393
|
+
})
|
|
394
|
+
|
|
395
|
+
// Determine wrapper flex-direction based on actual title_side
|
|
396
|
+
let wrapper_flex_dir = $derived(
|
|
397
|
+
{ left: `row`, right: `row-reverse`, top: `column`, bottom: `column-reverse` }[
|
|
398
|
+
actual_title_side
|
|
399
|
+
],
|
|
400
|
+
)
|
|
401
|
+
|
|
402
|
+
// CSS variables for bar width/height based on orientation
|
|
403
|
+
let final_bar_style = $derived(
|
|
404
|
+
`--cbar-width: ${
|
|
405
|
+
orientation === `horizontal` ? `100%` : `var(--cbar-thickness, 10px)`
|
|
406
|
+
};
|
|
407
|
+
--cbar-height: ${
|
|
408
|
+
orientation === `vertical` ? `100%` : `var(--cbar-thickness, 10px)`
|
|
409
|
+
};
|
|
410
|
+
background: linear-gradient(${grad_dir}, ${ramped.join(`, `)}); ${
|
|
411
|
+
bar_style ?? ``
|
|
412
|
+
}`,
|
|
413
|
+
)
|
|
414
|
+
|
|
415
|
+
// Calculate additional margin for main label if it overlaps with ticks
|
|
416
|
+
let label_overlap_margin_style = $derived.by(() => {
|
|
290
417
|
// Overlap only possible if ticks are outside and on same side as label
|
|
291
|
-
if (tick_side === `inside`)
|
|
292
|
-
|
|
418
|
+
if (tick_side === `inside`) return ``
|
|
419
|
+
|
|
293
420
|
// Determine concrete side outside ticks are on
|
|
294
421
|
const concrete_outside_tick_side = orientation === `horizontal`
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
const
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
422
|
+
? tick_side === `primary` ? `bottom` : `top`
|
|
423
|
+
: tick_side === `primary`
|
|
424
|
+
? `right`
|
|
425
|
+
: `left`
|
|
426
|
+
|
|
427
|
+
if (actual_title_side !== concrete_outside_tick_side) return ``
|
|
428
|
+
|
|
429
|
+
const offset = `var(--cbar-label-overlap-offset, 1em)`
|
|
430
|
+
|
|
431
|
+
const side_map = { top: `bottom`, bottom: `top`, left: `right`, right: `left` }
|
|
432
|
+
const margin_side = side_map[actual_title_side]
|
|
433
|
+
return `margin-${margin_side}: ${offset};`
|
|
434
|
+
})
|
|
435
|
+
|
|
436
|
+
// Derive whether we're in vertical side-label mode (label on left/right of vertical bar)
|
|
437
|
+
let is_vertical_side = $derived(
|
|
438
|
+
orientation === `vertical` &&
|
|
439
|
+
(actual_title_side === `left` || actual_title_side === `right`),
|
|
440
|
+
)
|
|
441
|
+
|
|
442
|
+
let actual_title_style = $derived.by(() => {
|
|
310
443
|
// No container-level transform - rotation is applied only to .label element via CSS
|
|
311
444
|
// This avoids breaking selects/dropdowns which need to remain horizontal
|
|
312
445
|
let size_constraint = is_vertical_side
|
|
313
|
-
|
|
314
|
-
|
|
446
|
+
? `max-width: var(--cbar-label-max-width, 2em);`
|
|
447
|
+
: ``
|
|
448
|
+
|
|
315
449
|
return `${size_constraint} ${label_overlap_margin_style} ${title_style ?? ``}`
|
|
316
|
-
|
|
317
|
-
})
|
|
318
|
-
|
|
450
|
+
.trim()
|
|
451
|
+
})
|
|
452
|
+
|
|
453
|
+
function get_tick_text_color(tick_value: number): string | null {
|
|
319
454
|
// Only apply dynamic color if ticks are inside bar
|
|
320
|
-
if (tick_side !== `inside`)
|
|
321
|
-
|
|
322
|
-
const bg_color = actual_color_scale_fn(tick_value)
|
|
455
|
+
if (tick_side !== `inside`) return null
|
|
456
|
+
|
|
457
|
+
const bg_color = actual_color_scale_fn(tick_value)
|
|
323
458
|
// Default to black if luminance calculation fails or color is invalid
|
|
324
459
|
try {
|
|
325
|
-
|
|
460
|
+
return luminance(bg_color) > 0.5 ? `black` : `white`
|
|
461
|
+
} catch (error) {
|
|
462
|
+
console.error(`Error calculating luminance for tick ${tick_value}:`, error)
|
|
463
|
+
return `black`
|
|
326
464
|
}
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
let
|
|
334
|
-
|
|
335
|
-
// Initialize selected keys to first option when options provided but key undefined
|
|
336
|
-
// This ensures state matches UI (which shows first option by default)
|
|
337
|
-
$effect(() => {
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
let has_property_select = $derived(property_options && property_options.length > 0)
|
|
468
|
+
let has_color_scale_select = $derived(
|
|
469
|
+
color_scale_options && color_scale_options.length > 0,
|
|
470
|
+
)
|
|
471
|
+
let has_any_select = $derived(has_property_select || has_color_scale_select)
|
|
472
|
+
|
|
473
|
+
// Initialize selected keys to first option when options provided but key undefined
|
|
474
|
+
// This ensures state matches UI (which shows first option by default)
|
|
475
|
+
$effect(() => {
|
|
338
476
|
if (has_property_select && selected_property_key === undefined) {
|
|
339
|
-
|
|
477
|
+
selected_property_key = property_options?.[0]?.key
|
|
340
478
|
}
|
|
341
|
-
})
|
|
342
|
-
$effect(() => {
|
|
479
|
+
})
|
|
480
|
+
$effect(() => {
|
|
343
481
|
if (has_color_scale_select && selected_color_scale_key === undefined) {
|
|
344
|
-
|
|
482
|
+
selected_color_scale_key = color_scale_options?.[0]?.key
|
|
345
483
|
}
|
|
346
|
-
})
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
484
|
+
})
|
|
485
|
+
|
|
486
|
+
async function handle_property_change(new_key: string, prev_key?: string) {
|
|
487
|
+
if (!data_loader) return
|
|
350
488
|
// Capture all state for full rollback on any error
|
|
351
489
|
// Note: prev_key comes from PortalSelect since binding updates before callback
|
|
352
490
|
// prev_key can be undefined if no prior selection - that's a valid rollback state
|
|
353
|
-
const prev = { title, range, selected_property_key: prev_key }
|
|
354
|
-
|
|
491
|
+
const prev = { title, range, selected_property_key: prev_key } as const
|
|
492
|
+
|
|
493
|
+
loading = true
|
|
494
|
+
|
|
355
495
|
try {
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
}
|
|
370
|
-
finally {
|
|
371
|
-
loading = false;
|
|
496
|
+
const result = await data_loader(new_key)
|
|
497
|
+
range = result.range
|
|
498
|
+
if (result.title !== undefined) title = result.title
|
|
499
|
+
// Isolate callback errors - still rollback if callback throws
|
|
500
|
+
on_property_change?.(new_key, result.range)
|
|
501
|
+
} catch (err) {
|
|
502
|
+
console.error(`ColorBar property change failed for ${new_key}:`, err)
|
|
503
|
+
// Full rollback of all state
|
|
504
|
+
selected_property_key = prev.selected_property_key
|
|
505
|
+
range = prev.range
|
|
506
|
+
title = prev.title
|
|
507
|
+
} finally {
|
|
508
|
+
loading = false
|
|
372
509
|
}
|
|
373
|
-
}
|
|
374
|
-
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
function handle_color_scale_change(new_key: string, prev_key?: string) {
|
|
375
513
|
// Find option - rollback binding if not found to keep key and scale in sync
|
|
376
|
-
const opt = color_scale_options?.find((item) => item.key === new_key)
|
|
514
|
+
const opt = color_scale_options?.find((item) => item.key === new_key)
|
|
377
515
|
if (!opt) {
|
|
378
|
-
|
|
379
|
-
|
|
516
|
+
selected_color_scale_key = prev_key
|
|
517
|
+
return
|
|
380
518
|
}
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
519
|
+
|
|
520
|
+
color_scale = opt.scale
|
|
521
|
+
on_color_scale_change?.(new_key)
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
// Align items based on orientation and title position
|
|
525
|
+
let div_style = $derived(`
|
|
526
|
+
--cbar-wrapper-align-items: ${
|
|
527
|
+
orientation === `vertical` &&
|
|
528
|
+
(actual_title_side === `left` || actual_title_side === `right`)
|
|
529
|
+
? `stretch`
|
|
530
|
+
: `center`
|
|
531
|
+
};
|
|
532
|
+
--cbar-label-display: ${
|
|
533
|
+
orientation === `vertical` &&
|
|
534
|
+
(actual_title_side === `left` || actual_title_side === `right`)
|
|
535
|
+
? `flex`
|
|
536
|
+
: `inline-block`
|
|
537
|
+
};
|
|
538
|
+
height: ${
|
|
539
|
+
orientation === `vertical`
|
|
540
|
+
? `var(--cbar-height, 100%)`
|
|
541
|
+
: `var(--cbar-height, auto)`
|
|
542
|
+
};
|
|
543
|
+
min-height: ${
|
|
544
|
+
orientation === `vertical` ? `var(--cbar-min-height, 150px)` : `auto`
|
|
545
|
+
};
|
|
546
|
+
max-height: ${
|
|
547
|
+
orientation === `vertical` ? `var(--cbar-max-height, 1000px)` : `none`
|
|
548
|
+
}; ${wrapper_style ?? ``}`)
|
|
399
549
|
</script>
|
|
400
550
|
|
|
401
551
|
<div
|
|
@@ -421,7 +571,7 @@ let div_style = $derived(`
|
|
|
421
571
|
{/if}
|
|
422
572
|
{:else if title}
|
|
423
573
|
<!-- Only show static title if no property select -->
|
|
424
|
-
<span class="label">{@html title}</span>
|
|
574
|
+
<span class="label">{@html sanitize_html(title)}</span>
|
|
425
575
|
{/if}
|
|
426
576
|
{#if has_color_scale_select && color_scale_options}
|
|
427
577
|
<PortalSelect
|