matterviz 0.3.2 → 0.3.4

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.
Files changed (281) hide show
  1. package/dist/EmptyState.svelte +10 -2
  2. package/dist/FilePicker.svelte +123 -82
  3. package/dist/Icon.svelte +18 -12
  4. package/dist/MillerIndexInput.svelte +27 -21
  5. package/dist/api/optimade.js +6 -6
  6. package/dist/app.css +216 -207
  7. package/dist/brillouin/BrillouinZone.svelte +292 -149
  8. package/dist/brillouin/BrillouinZone.svelte.d.ts +1 -1
  9. package/dist/brillouin/BrillouinZoneControls.svelte +32 -5
  10. package/dist/brillouin/BrillouinZoneExportPane.svelte +69 -42
  11. package/dist/brillouin/BrillouinZoneExportPane.svelte.d.ts +1 -1
  12. package/dist/brillouin/BrillouinZoneInfoPane.svelte +99 -68
  13. package/dist/brillouin/BrillouinZoneScene.svelte +275 -163
  14. package/dist/brillouin/BrillouinZoneScene.svelte.d.ts +1 -1
  15. package/dist/brillouin/BrillouinZoneTooltip.svelte +17 -7
  16. package/dist/brillouin/compute.js +11 -6
  17. package/dist/chempot-diagram/ChemPotDiagram.svelte +162 -27
  18. package/dist/chempot-diagram/ChemPotDiagram2D.svelte +451 -281
  19. package/dist/chempot-diagram/ChemPotDiagram3D.svelte +2148 -1642
  20. package/dist/chempot-diagram/ChemPotScene3D.svelte +8 -5
  21. package/dist/chempot-diagram/async-compute.svelte.d.ts +3 -0
  22. package/dist/chempot-diagram/async-compute.svelte.js +77 -0
  23. package/dist/chempot-diagram/chempot-worker.d.ts +1 -0
  24. package/dist/chempot-diagram/chempot-worker.js +11 -0
  25. package/dist/chempot-diagram/color.js +1 -2
  26. package/dist/chempot-diagram/compute.d.ts +10 -0
  27. package/dist/chempot-diagram/compute.js +250 -88
  28. package/dist/chempot-diagram/index.d.ts +2 -1
  29. package/dist/chempot-diagram/index.js +2 -1
  30. package/dist/chempot-diagram/temperature.js +8 -9
  31. package/dist/chempot-diagram/types.d.ts +3 -0
  32. package/dist/chempot-diagram/types.js +1 -0
  33. package/dist/colors/index.d.ts +1 -1
  34. package/dist/colors/index.js +5 -3
  35. package/dist/composition/BarChart.svelte +128 -55
  36. package/dist/composition/BubbleChart.svelte +102 -49
  37. package/dist/composition/Composition.svelte +100 -79
  38. package/dist/composition/Formula.svelte +108 -62
  39. package/dist/composition/FormulaFilter.svelte +665 -537
  40. package/dist/composition/PieChart.svelte +183 -108
  41. package/dist/composition/format.d.ts +5 -0
  42. package/dist/composition/format.js +20 -3
  43. package/dist/composition/parse.js +14 -9
  44. package/dist/convex-hull/ConvexHull.svelte +93 -40
  45. package/dist/convex-hull/ConvexHull.svelte.d.ts +1 -1
  46. package/dist/convex-hull/ConvexHull2D.svelte +549 -360
  47. package/dist/convex-hull/ConvexHull2D.svelte.d.ts +1 -1
  48. package/dist/convex-hull/ConvexHull3D.svelte +1296 -827
  49. package/dist/convex-hull/ConvexHull3D.svelte.d.ts +1 -1
  50. package/dist/convex-hull/ConvexHull4D.svelte +1004 -688
  51. package/dist/convex-hull/ConvexHull4D.svelte.d.ts +1 -1
  52. package/dist/convex-hull/ConvexHullControls.svelte +115 -28
  53. package/dist/convex-hull/ConvexHullControls.svelte.d.ts +1 -1
  54. package/dist/convex-hull/ConvexHullInfoPane.svelte +29 -3
  55. package/dist/convex-hull/ConvexHullStats.svelte +425 -328
  56. package/dist/convex-hull/ConvexHullTooltip.svelte +40 -16
  57. package/dist/convex-hull/GasPressureControls.svelte +104 -61
  58. package/dist/convex-hull/StructurePopup.svelte +25 -4
  59. package/dist/convex-hull/TemperatureSlider.svelte +45 -25
  60. package/dist/convex-hull/barycentric-coords.js +13 -7
  61. package/dist/convex-hull/demo-temperature.js +8 -4
  62. package/dist/convex-hull/gas-thermodynamics.js +17 -12
  63. package/dist/convex-hull/helpers.d.ts +9 -0
  64. package/dist/convex-hull/helpers.js +77 -34
  65. package/dist/convex-hull/thermodynamics.js +61 -56
  66. package/dist/convex-hull/types.d.ts +9 -14
  67. package/dist/convex-hull/types.js +0 -17
  68. package/dist/coordination/CoordinationBarPlot.svelte +227 -154
  69. package/dist/element/BohrAtom.svelte +55 -12
  70. package/dist/element/ElementHeading.svelte +7 -2
  71. package/dist/element/ElementPhoto.svelte +15 -9
  72. package/dist/element/ElementStats.svelte +10 -4
  73. package/dist/element/ElementTile.svelte +137 -73
  74. package/dist/element/Nucleus.svelte +39 -11
  75. package/dist/element/data.js +1 -1
  76. package/dist/feedback/ClickFeedback.svelte +16 -5
  77. package/dist/feedback/DragOverlay.svelte +10 -2
  78. package/dist/feedback/Spinner.svelte +4 -2
  79. package/dist/feedback/StatusMessage.svelte +8 -2
  80. package/dist/fermi-surface/FermiSlice.svelte +118 -88
  81. package/dist/fermi-surface/FermiSurface.svelte +328 -187
  82. package/dist/fermi-surface/FermiSurface.svelte.d.ts +1 -1
  83. package/dist/fermi-surface/FermiSurfaceControls.svelte +113 -46
  84. package/dist/fermi-surface/FermiSurfaceControls.svelte.d.ts +1 -1
  85. package/dist/fermi-surface/FermiSurfaceScene.svelte +535 -342
  86. package/dist/fermi-surface/FermiSurfaceScene.svelte.d.ts +1 -1
  87. package/dist/fermi-surface/FermiSurfaceTooltip.svelte +14 -5
  88. package/dist/fermi-surface/compute.js +16 -20
  89. package/dist/fermi-surface/parse.js +24 -14
  90. package/dist/fermi-surface/symmetry.js +2 -7
  91. package/dist/fermi-surface/types.d.ts +3 -5
  92. package/dist/heatmap-matrix/HeatmapMatrix.svelte +1019 -765
  93. package/dist/heatmap-matrix/HeatmapMatrix.svelte.d.ts +1 -1
  94. package/dist/heatmap-matrix/HeatmapMatrixControls.svelte +76 -22
  95. package/dist/heatmap-matrix/HeatmapMatrixControls.svelte.d.ts +2 -3
  96. package/dist/icons.js +47 -0
  97. package/dist/index.d.ts +2 -1
  98. package/dist/index.js +2 -1
  99. package/dist/io/decompress.js +1 -1
  100. package/dist/io/export.d.ts +3 -0
  101. package/dist/io/export.js +129 -143
  102. package/dist/io/is-binary.js +2 -3
  103. package/dist/io/url-drop.js +1 -2
  104. package/dist/isosurface/Isosurface.svelte +202 -148
  105. package/dist/isosurface/IsosurfaceControls.svelte +46 -28
  106. package/dist/isosurface/parse.js +34 -29
  107. package/dist/isosurface/slice.js +5 -10
  108. package/dist/isosurface/types.d.ts +2 -1
  109. package/dist/isosurface/types.js +61 -12
  110. package/dist/labels.js +11 -8
  111. package/dist/layout/FullscreenToggle.svelte +11 -2
  112. package/dist/layout/InfoCard.svelte +38 -6
  113. package/dist/layout/InfoTag.svelte +63 -32
  114. package/dist/layout/PropertyFilter.svelte +82 -37
  115. package/dist/layout/SettingsSection.svelte +85 -55
  116. package/dist/layout/SubpageGrid.svelte +10 -2
  117. package/dist/layout/json-tree/JsonNode.svelte +183 -138
  118. package/dist/layout/json-tree/JsonTree.svelte +499 -413
  119. package/dist/layout/json-tree/JsonValue.svelte +127 -99
  120. package/dist/layout/json-tree/utils.js +4 -2
  121. package/dist/marching-cubes.js +25 -2
  122. package/dist/math.d.ts +13 -17
  123. package/dist/math.js +133 -67
  124. package/dist/overlays/ContextMenu.svelte +65 -40
  125. package/dist/overlays/DraggablePane.svelte +211 -139
  126. package/dist/periodic-table/PeriodicTable.svelte +278 -145
  127. package/dist/periodic-table/PeriodicTableControls.svelte +178 -128
  128. package/dist/periodic-table/PropertySelect.svelte +25 -7
  129. package/dist/periodic-table/TableInset.svelte +8 -3
  130. package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte +446 -309
  131. package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte.d.ts +1 -1
  132. package/dist/phase-diagram/PhaseDiagramControls.svelte +102 -43
  133. package/dist/phase-diagram/PhaseDiagramControls.svelte.d.ts +1 -1
  134. package/dist/phase-diagram/PhaseDiagramEditorPane.svelte +63 -40
  135. package/dist/phase-diagram/PhaseDiagramExportPane.svelte +71 -28
  136. package/dist/phase-diagram/PhaseDiagramExportPane.svelte.d.ts +1 -1
  137. package/dist/phase-diagram/PhaseDiagramTooltip.svelte +158 -101
  138. package/dist/phase-diagram/TdbInfoPanel.svelte +28 -4
  139. package/dist/phase-diagram/build-diagram.js +9 -9
  140. package/dist/phase-diagram/colors.js +1 -3
  141. package/dist/phase-diagram/parse.js +10 -9
  142. package/dist/phase-diagram/svg-to-diagram.js +53 -49
  143. package/dist/phase-diagram/utils.d.ts +1 -0
  144. package/dist/phase-diagram/utils.js +80 -25
  145. package/dist/plot/AxisLabel.svelte +28 -3
  146. package/dist/plot/BarPlot.svelte +1182 -734
  147. package/dist/plot/BarPlot.svelte.d.ts +2 -2
  148. package/dist/plot/BarPlotControls.svelte +31 -5
  149. package/dist/plot/BarPlotControls.svelte.d.ts +1 -1
  150. package/dist/plot/ColorBar.svelte +479 -329
  151. package/dist/plot/ColorScaleSelect.svelte +27 -6
  152. package/dist/plot/ElementScatter.svelte +36 -15
  153. package/dist/plot/FillArea.svelte +152 -95
  154. package/dist/plot/Histogram.svelte +934 -571
  155. package/dist/plot/Histogram.svelte.d.ts +1 -1
  156. package/dist/plot/HistogramControls.svelte +53 -9
  157. package/dist/plot/HistogramControls.svelte.d.ts +1 -1
  158. package/dist/plot/InteractiveAxisLabel.svelte +34 -11
  159. package/dist/plot/InteractiveAxisLabel.svelte.d.ts +1 -1
  160. package/dist/plot/Line.svelte +63 -28
  161. package/dist/plot/PlotControls.svelte +157 -114
  162. package/dist/plot/PlotControls.svelte.d.ts +1 -1
  163. package/dist/plot/PlotLegend.svelte +174 -91
  164. package/dist/plot/PlotTooltip.svelte +45 -6
  165. package/dist/plot/PortalSelect.svelte +175 -147
  166. package/dist/plot/ReferenceLine.svelte +76 -22
  167. package/dist/plot/ReferenceLine3D.svelte +132 -107
  168. package/dist/plot/ReferencePlane.svelte +146 -121
  169. package/dist/plot/ScatterPlot.svelte +1681 -1091
  170. package/dist/plot/ScatterPlot.svelte.d.ts +2 -2
  171. package/dist/plot/ScatterPlot3D.svelte +256 -131
  172. package/dist/plot/ScatterPlot3D.svelte.d.ts +2 -2
  173. package/dist/plot/ScatterPlot3DControls.svelte +113 -63
  174. package/dist/plot/ScatterPlot3DControls.svelte.d.ts +2 -1
  175. package/dist/plot/ScatterPlot3DScene.svelte +608 -403
  176. package/dist/plot/ScatterPlot3DScene.svelte.d.ts +2 -2
  177. package/dist/plot/ScatterPlotControls.svelte +65 -25
  178. package/dist/plot/ScatterPlotControls.svelte.d.ts +1 -1
  179. package/dist/plot/ScatterPoint.svelte +98 -26
  180. package/dist/plot/ScatterPoint.svelte.d.ts +1 -0
  181. package/dist/plot/SpacegroupBarPlot.svelte +142 -85
  182. package/dist/plot/Surface3D.svelte +159 -108
  183. package/dist/plot/ZeroLines.svelte +55 -3
  184. package/dist/plot/ZoomRect.svelte +4 -2
  185. package/dist/plot/axis-utils.js +1 -3
  186. package/dist/plot/data-cleaning.js +12 -28
  187. package/dist/plot/data-transform.js +2 -1
  188. package/dist/plot/fill-utils.js +2 -0
  189. package/dist/plot/layout.d.ts +4 -1
  190. package/dist/plot/layout.js +33 -14
  191. package/dist/plot/reference-line.d.ts +2 -2
  192. package/dist/plot/reference-line.js +7 -5
  193. package/dist/plot/scales.js +24 -36
  194. package/dist/plot/types.d.ts +11 -23
  195. package/dist/plot/types.js +6 -11
  196. package/dist/plot/utils/label-placement.d.ts +32 -15
  197. package/dist/plot/utils/label-placement.js +227 -66
  198. package/dist/plot/utils/series-visibility.js +2 -3
  199. package/dist/rdf/RdfPlot.svelte +143 -91
  200. package/dist/rdf/calc-rdf.js +4 -5
  201. package/dist/sanitize.d.ts +4 -0
  202. package/dist/sanitize.js +107 -0
  203. package/dist/settings.d.ts +18 -6
  204. package/dist/settings.js +46 -16
  205. package/dist/spectral/Bands.svelte +632 -453
  206. package/dist/spectral/BandsAndDos.svelte +90 -49
  207. package/dist/spectral/BrillouinBandsDos.svelte +151 -93
  208. package/dist/spectral/Dos.svelte +389 -258
  209. package/dist/spectral/helpers.js +55 -43
  210. package/dist/state.svelte.d.ts +1 -1
  211. package/dist/state.svelte.js +3 -2
  212. package/dist/structure/Arrow.svelte +59 -20
  213. package/dist/structure/AtomLegend.svelte +215 -134
  214. package/dist/structure/Bond.svelte +73 -47
  215. package/dist/structure/CanvasTooltip.svelte +10 -2
  216. package/dist/structure/CellSelect.svelte +72 -45
  217. package/dist/structure/Cylinder.svelte +33 -17
  218. package/dist/structure/Lattice.svelte +88 -33
  219. package/dist/structure/Structure.svelte +1063 -797
  220. package/dist/structure/Structure.svelte.d.ts +1 -1
  221. package/dist/structure/StructureControls.svelte +349 -118
  222. package/dist/structure/StructureExportPane.svelte +124 -89
  223. package/dist/structure/StructureExportPane.svelte.d.ts +1 -1
  224. package/dist/structure/StructureInfoPane.svelte +304 -237
  225. package/dist/structure/StructureScene.svelte +879 -443
  226. package/dist/structure/StructureScene.svelte.d.ts +15 -7
  227. package/dist/structure/atom-properties.js +8 -8
  228. package/dist/structure/bonding.js +6 -7
  229. package/dist/structure/export.js +14 -29
  230. package/dist/structure/ferrox-wasm.js +1 -1
  231. package/dist/structure/index.d.ts +13 -3
  232. package/dist/structure/index.js +83 -23
  233. package/dist/structure/measure.d.ts +2 -2
  234. package/dist/structure/measure.js +4 -44
  235. package/dist/structure/parse.js +113 -141
  236. package/dist/structure/partial-occupancy.js +7 -10
  237. package/dist/structure/pbc.d.ts +1 -0
  238. package/dist/structure/pbc.js +16 -6
  239. package/dist/structure/supercell.d.ts +2 -2
  240. package/dist/structure/supercell.js +12 -22
  241. package/dist/structure/validation.js +1 -2
  242. package/dist/symmetry/SymmetryStats.svelte +84 -41
  243. package/dist/symmetry/WyckoffTable.svelte +26 -6
  244. package/dist/symmetry/cell-transform.js +5 -3
  245. package/dist/symmetry/index.js +8 -7
  246. package/dist/symmetry/spacegroups.js +148 -148
  247. package/dist/table/HeatmapTable.svelte +790 -554
  248. package/dist/table/HeatmapTable.svelte.d.ts +1 -1
  249. package/dist/table/ToggleMenu.svelte +125 -92
  250. package/dist/table/index.js +2 -4
  251. package/dist/theme/ThemeControl.svelte +21 -12
  252. package/dist/time.js +4 -1
  253. package/dist/tooltip/TooltipContent.svelte +33 -8
  254. package/dist/trajectory/Trajectory.svelte +758 -558
  255. package/dist/trajectory/TrajectoryError.svelte +14 -3
  256. package/dist/trajectory/TrajectoryExportPane.svelte +137 -83
  257. package/dist/trajectory/TrajectoryInfoPane.svelte +272 -143
  258. package/dist/trajectory/extract.js +10 -26
  259. package/dist/trajectory/format-detect.js +5 -5
  260. package/dist/trajectory/frame-reader.d.ts +1 -1
  261. package/dist/trajectory/frame-reader.js +5 -12
  262. package/dist/trajectory/helpers.d.ts +0 -1
  263. package/dist/trajectory/helpers.js +2 -17
  264. package/dist/trajectory/index.js +14 -12
  265. package/dist/trajectory/parse/ase.js +5 -4
  266. package/dist/trajectory/parse/hdf5.js +26 -18
  267. package/dist/trajectory/parse/index.js +13 -18
  268. package/dist/trajectory/parse/lammps.js +17 -7
  269. package/dist/trajectory/parse/vasp.js +5 -2
  270. package/dist/trajectory/parse/xyz.js +8 -7
  271. package/dist/trajectory/plotting.js +13 -8
  272. package/dist/utils.d.ts +1 -0
  273. package/dist/utils.js +13 -0
  274. package/dist/xrd/XrdPlot.svelte +337 -247
  275. package/dist/xrd/broadening.js +14 -9
  276. package/dist/xrd/calc-xrd.js +12 -18
  277. package/dist/xrd/parse.d.ts +1 -1
  278. package/dist/xrd/parse.js +17 -17
  279. package/package.json +99 -103
  280. package/readme.md +1 -1
  281. /package/dist/theme/{themes.js → themes.mjs} +0 -0
@@ -1,401 +1,551 @@
1
- <script lang="ts">import { luminance } from '../colors';
2
- import Spinner from '../feedback/Spinner.svelte';
3
- import { format_num } from '../labels';
4
- import * as math from '../math';
5
- import { format } from 'd3-format';
6
- import * as d3 from 'd3-scale';
7
- import * as d3_sc from 'd3-scale-chromatic';
8
- import { timeFormat } from 'd3-time-format';
9
- import PortalSelect from './PortalSelect.svelte';
10
- import { generate_arcsinh_ticks, scale_arcsinh } from './scales';
11
- import { get_arcsinh_threshold, get_scale_type_name } from './types';
12
- let { title = $bindable(), color_scale = $bindable(`interpolateViridis`), bar_style = undefined, title_style = undefined, wrapper_style = undefined, tick_labels = $bindable(4), tick_format = undefined, range = $bindable([0, 1]), orientation = `horizontal`, snap_ticks = true, steps = 50, nice_range = $bindable(range), title_side = undefined, // no default here, depends on orientation and tick_side
13
- tick_side = `primary`, scale_type = `linear`, color_scale_fn = undefined, color_scale_domain = undefined,
14
- // Property selection (interactive title)
15
- property_options = undefined, selected_property_key = $bindable(), data_loader = undefined, on_property_change = undefined,
16
- // Color scale selection
17
- color_scale_options = undefined, selected_color_scale_key = $bindable(), on_color_scale_change = undefined, ...rest } = $props();
18
- // Loading state for property data fetching
19
- let loading = $state(false);
20
- let actual_title_side = $derived.by(() => {
21
- if (title_side !== undefined)
22
- return title_side; // Use user-provided value if available
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
- return `left`; // Default to left if ticks are inside
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
- return tick_side === `primary` ? `top` : `bottom`;
30
- }
31
- else { // orientation === `vertical`
32
- // If ticks are primary (right), default label to left
33
- // If ticks are secondary (left), default label to right
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
- // Number of ticks to generate
38
- let n_ticks = $derived(Array.isArray(tick_labels)
39
- ? tick_labels.length
40
- : typeof tick_labels === `number`
41
- ? tick_labels
42
- : 5);
43
- // Scale for ticks - based *only* on 'range' prop and 'scale_type' for ticks
44
- let scale_for_ticks = $derived.by(() => {
45
- const type_name = get_scale_type_name(scale_type);
46
- let use_log_for_ticks = type_name === `log`;
47
- let [scale_min, scale_max] = range;
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
- if (scale_max <= 0) {
51
- console.warn(`Log scale requires a positive max value for ticks. Received max=${scale_max}. Using linear scale for ticks instead.`);
52
- use_log_for_ticks = false;
53
- }
54
- else if (scale_min <= 0) {
55
- console.warn(`Log scale received non-positive min value (${scale_min}) for ticks. Using epsilon=${math.LOG_EPS} instead.`);
56
- scale_min = math.LOG_EPS; // Substitute with epsilon
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
- // Guard against very small thresholds that could cause precision issues
62
- const threshold = Math.max(get_arcsinh_threshold(scale_type), Number.EPSILON);
63
- const scale = scale_arcsinh(threshold)
64
- .domain([scale_min, scale_max])
65
- .range(orientation === `vertical` ? [100, 0] : [0, 100]);
66
- return scale;
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
- const scale = use_log_for_ticks ? d3.scaleLog() : d3.scaleLinear();
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
- scale.nice(n_ticks);
162
+ scale.nice(n_ticks)
76
163
  }
77
- return scale;
78
- });
79
- let ticks_array = $derived.by(() => {
164
+
165
+ return scale
166
+ })
167
+
168
+ let ticks_array: number[] = $derived.by(() => {
80
169
  if (Array.isArray(tick_labels)) {
81
- // Use user-provided ticks directly
82
- return tick_labels.map(Number).filter((n) => !isNaN(n));
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
- return [];
87
- if (n_ticks === 1)
88
- return [scale_for_ticks.domain()[0]];
89
- const scale = scale_for_ticks; // Use derived scale (which handles log validation for ticks)
90
- const [scale_min, scale_max] = scale.domain();
91
- const type_name = get_scale_type_name(scale_type);
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
- // Guard against very small thresholds that could cause precision issues
95
- const threshold = Math.max(get_arcsinh_threshold(scale_type), Number.EPSILON);
96
- return generate_arcsinh_ticks(scale_min, scale_max, threshold, n_ticks);
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
- // Use D3's ticks for log scale if snapping is enabled
102
- if (snap_ticks) {
103
- // For snapped log ticks, manually generate integer powers of 10 within niced domain.
104
- const [nice_min, nice_max] = scale.domain();
105
- const start_exp = Math.ceil(Math.log10(nice_min));
106
- const end_exp = Math.floor(Math.log10(nice_max));
107
- const power_of_10_ticks = [];
108
- for (let exp = start_exp; exp <= end_exp; exp++) {
109
- power_of_10_ticks.push(Math.pow(10, exp));
110
- }
111
- // Ensure domain endpoints are included if they are powers of 10 and missed by loop
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
- else {
142
- // Use D3's default nice ticks for linear scale
143
- if (snap_ticks)
144
- return scale.ticks(n_ticks);
145
- else {
146
- // Generate exactly n_ticks evenly spaced linear ticks
147
- return [...Array(n_ticks).keys()].map((idx) => {
148
- const t = idx / (n_ticks - 1);
149
- return scale_min + t * (scale_max - scale_min);
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
- // Update nice_range binding when snapping ticks
155
- $effect.pre(() => {
249
+ })
250
+
251
+ // Update nice_range binding when snapping ticks
252
+ $effect.pre(() => {
156
253
  if (snap_ticks && !Array.isArray(tick_labels)) {
157
- // Use derived scale to get niced domain
158
- const domain = scale_for_ticks.domain();
159
- // Ensure domain has two elements before assigning
160
- if (domain.length === 2)
161
- nice_range = domain;
162
- else
163
- nice_range = range; // Fallback
164
- }
165
- else
166
- nice_range = range; // Use original range if not snapping or labels provided
167
- });
168
- // Determine effective color scale function to use
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; // Default interpolator
267
+ let interpolator = d3_sc.interpolateViridis // Default interpolator
174
268
  if (typeof color_scale === `string`) {
175
- const func_name = color_scale.startsWith(`interpolate`)
176
- ? color_scale
177
- : `interpolate${color_scale}`;
178
- if (func_name in d3_sc) {
179
- interpolator = d3_sc[func_name];
180
- }
181
- else {
182
- console.error(`Color scale '${color_scale}' not found. Falling back on 'Viridis'.`);
183
- }
184
- }
185
- else if (typeof color_scale === `function`) {
186
- // User passed a function (assumed interpolator [0,1] -> color)
187
- interpolator = color_scale;
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
- if (max_val <= 0) {
196
- console.warn(`Log scale requires a positive max value for fallback scale. Received max=${max_val}. Using linear scale for colors.`);
197
- use_log_fallback = false;
198
- }
199
- else if (min_val <= 0) {
200
- console.warn(`Log scale received non-positive min value (${min_val}) for fallback scale. Using epsilon=${math.LOG_EPS} instead.`);
201
- min_val = math.LOG_EPS; // Substitute with epsilon
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
- // Guard against very small thresholds that could cause precision issues
211
- const threshold = Math.max(get_arcsinh_threshold(scale_type), Number.EPSILON);
212
- const t_min = Math.asinh(lo / threshold);
213
- const t_max = Math.asinh(hi / threshold);
214
- return (value) => {
215
- const t_val = Math.asinh(value / threshold);
216
- const normalized = t_max === t_min ? 0.5 : (t_val - t_min) / (t_max - t_min);
217
- return interpolator(Math.max(0, Math.min(1, normalized)));
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
- ? d3.scaleSequentialLog(interpolator).domain(domain_for_scale)
222
- : d3.scaleSequential(interpolator).domain(domain_for_scale);
223
- });
224
- // Determine effective domain for color ramp interpolation *steps*
225
- // Prioritize color_scale_domain if provided, otherwise use general 'range' prop.
226
- let color_interp_domain = $derived(color_scale_domain ?? range);
227
- let grad_dir = $derived(orientation === `horizontal` ? `to right` : `to top`);
228
- // Generate color stops for gradient background using effective scale and domain
229
- let ramped = $derived.by(() => {
230
- const [min_ramp_domain, max_ramp_domain] = color_interp_domain;
231
- const type_name = get_scale_type_name(scale_type);
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
- if (max_ramp_domain <= 0) {
238
- console.warn(`Log scale specified for gradient, but max domain value (${max_ramp_domain}) is not positive. Using linear interpolation.`);
239
- use_log_interp = false;
240
- }
241
- else if (min_ramp_domain <= 0) {
242
- console.warn(`Log scale specified for gradient, but min domain value (${min_ramp_domain}) is not positive. Using epsilon=${math.LOG_EPS} instead.`);
243
- adjusted_min_ramp = math.LOG_EPS; // Substitute with epsilon
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
- const n_steps = Math.max(2, Math.floor(steps)); // guard against steps <= 1 to avoid NaN/degenerate gradients
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
- log_min = Math.log10(adjusted_min_ramp);
253
- log_max = Math.log10(adjusted_max_ramp);
254
- log_span = log_max - log_min;
255
- }
256
- else if (type_name === `arcsinh`) {
257
- // Guard against very small thresholds that could cause precision issues
258
- asinh_threshold = Math.max(get_arcsinh_threshold(scale_type), Number.EPSILON);
259
- asinh_min = Math.asinh(min_ramp_domain / asinh_threshold);
260
- asinh_max = Math.asinh(max_ramp_domain / asinh_threshold);
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
- const t = idx / (n_steps - 1); // Normalized position 0 to 1
265
- let data_value;
266
- if (use_log_interp) {
267
- data_value = log_span === 0
268
- ? adjusted_min_ramp
269
- : Math.pow(10, log_min + t * log_span);
270
- }
271
- else if (type_name === `arcsinh`) {
272
- data_value = asinh_span === 0
273
- ? min_ramp_domain
274
- : Math.sinh(asinh_min + t * asinh_span) * asinh_threshold;
275
- }
276
- else {
277
- data_value = min_ramp_domain + t * linear_span;
278
- }
279
- return actual_color_scale_fn(data_value) ?? `transparent`;
280
- });
281
- });
282
- // Determine wrapper flex-direction based on actual title_side
283
- let wrapper_flex_dir = $derived({ left: `row`, right: `row-reverse`, top: `column`, bottom: `column-reverse` }[actual_title_side]);
284
- // CSS variables for bar width/height based on orientation
285
- let final_bar_style = $derived(`--cbar-width: ${orientation === `horizontal` ? `100%` : `var(--cbar-thickness, 14px)`};
286
- --cbar-height: ${orientation === `vertical` ? `100%` : `var(--cbar-thickness, 14px)`};
287
- background: linear-gradient(${grad_dir}, ${ramped.join(`, `)}); ${bar_style ?? ``}`);
288
- // Calculate additional margin for main label if it overlaps with ticks
289
- let label_overlap_margin_style = $derived.by(() => {
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
- return ``;
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
- ? tick_side === `primary` ? `bottom` : `top`
296
- : tick_side === `primary`
297
- ? `right`
298
- : `left`;
299
- if (actual_title_side !== concrete_outside_tick_side)
300
- return ``;
301
- const offset = `var(--cbar-label-overlap-offset, 1em)`;
302
- const side_map = { top: `bottom`, bottom: `top`, left: `right`, right: `left` };
303
- const margin_side = side_map[actual_title_side];
304
- return `margin-${margin_side}: ${offset};`;
305
- });
306
- // Derive whether we're in vertical side-label mode (label on left/right of vertical bar)
307
- let is_vertical_side = $derived(orientation === `vertical` &&
308
- (actual_title_side === `left` || actual_title_side === `right`));
309
- let actual_title_style = $derived.by(() => {
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
- ? `max-width: var(--cbar-label-max-width, 2em);`
314
- : ``;
446
+ ? `max-width: var(--cbar-label-max-width, 2em);`
447
+ : ``
448
+
315
449
  return `${size_constraint} ${label_overlap_margin_style} ${title_style ?? ``}`
316
- .trim();
317
- });
318
- function get_tick_text_color(tick_value) {
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
- return null;
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
- return luminance(bg_color) > 0.5 ? `black` : `white`;
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
- catch (error) {
328
- console.error(`Error calculating luminance for tick ${tick_value}:`, error);
329
- return `black`;
330
- }
331
- }
332
- let has_property_select = $derived(property_options && property_options.length > 0);
333
- let has_color_scale_select = $derived(color_scale_options && color_scale_options.length > 0);
334
- let has_any_select = $derived(has_property_select || has_color_scale_select);
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
- selected_property_key = property_options[0].key;
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
- selected_color_scale_key = color_scale_options[0].key;
482
+ selected_color_scale_key = color_scale_options?.[0]?.key
345
483
  }
346
- });
347
- async function handle_property_change(new_key, prev_key) {
348
- if (!data_loader)
349
- return;
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
- loading = true;
491
+ const prev = { title, range, selected_property_key: prev_key } as const
492
+
493
+ loading = true
494
+
355
495
  try {
356
- const result = await data_loader(new_key);
357
- range = result.range;
358
- if (result.title !== undefined)
359
- title = result.title;
360
- // Isolate callback errors - still rollback if callback throws
361
- on_property_change?.(new_key, result.range);
362
- }
363
- catch (err) {
364
- console.error(`ColorBar property change failed for ${new_key}:`, err);
365
- // Full rollback of all state
366
- selected_property_key = prev.selected_property_key;
367
- range = prev.range;
368
- title = prev.title;
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
- function handle_color_scale_change(new_key, prev_key) {
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
- selected_color_scale_key = prev_key;
379
- return;
516
+ selected_color_scale_key = prev_key
517
+ return
380
518
  }
381
- color_scale = opt.scale;
382
- on_color_scale_change?.(new_key);
383
- }
384
- // Align items based on orientation and title position
385
- let div_style = $derived(`
386
- --cbar-wrapper-align-items: ${orientation === `vertical` &&
387
- (actual_title_side === `left` || actual_title_side === `right`)
388
- ? `stretch`
389
- : `center`};
390
- --cbar-label-display: ${orientation === `vertical` &&
391
- (actual_title_side === `left` || actual_title_side === `right`)
392
- ? `flex`
393
- : `inline-block`};
394
- height: ${orientation === `vertical`
395
- ? `var(--cbar-height, 100%)`
396
- : `var(--cbar-height, auto)`};
397
- min-height: ${orientation === `vertical` ? `var(--cbar-min-height, 150px)` : `auto`};
398
- max-height: ${orientation === `vertical` ? `var(--cbar-max-height, 1000px)` : `none`}; ${wrapper_style ?? ``}`);
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