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.
Files changed (280) 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/feedback/ClickFeedback.svelte +16 -5
  76. package/dist/feedback/DragOverlay.svelte +10 -2
  77. package/dist/feedback/Spinner.svelte +4 -2
  78. package/dist/feedback/StatusMessage.svelte +8 -2
  79. package/dist/fermi-surface/FermiSlice.svelte +118 -88
  80. package/dist/fermi-surface/FermiSurface.svelte +328 -187
  81. package/dist/fermi-surface/FermiSurface.svelte.d.ts +1 -1
  82. package/dist/fermi-surface/FermiSurfaceControls.svelte +113 -46
  83. package/dist/fermi-surface/FermiSurfaceControls.svelte.d.ts +1 -1
  84. package/dist/fermi-surface/FermiSurfaceScene.svelte +535 -342
  85. package/dist/fermi-surface/FermiSurfaceScene.svelte.d.ts +1 -1
  86. package/dist/fermi-surface/FermiSurfaceTooltip.svelte +14 -5
  87. package/dist/fermi-surface/compute.js +16 -20
  88. package/dist/fermi-surface/parse.js +24 -14
  89. package/dist/fermi-surface/symmetry.js +2 -7
  90. package/dist/fermi-surface/types.d.ts +3 -5
  91. package/dist/heatmap-matrix/HeatmapMatrix.svelte +1019 -765
  92. package/dist/heatmap-matrix/HeatmapMatrix.svelte.d.ts +1 -1
  93. package/dist/heatmap-matrix/HeatmapMatrixControls.svelte +76 -22
  94. package/dist/heatmap-matrix/HeatmapMatrixControls.svelte.d.ts +2 -3
  95. package/dist/icons.js +47 -0
  96. package/dist/index.d.ts +2 -1
  97. package/dist/index.js +2 -1
  98. package/dist/io/decompress.js +1 -1
  99. package/dist/io/export.d.ts +3 -0
  100. package/dist/io/export.js +129 -143
  101. package/dist/io/is-binary.js +2 -3
  102. package/dist/io/url-drop.js +1 -2
  103. package/dist/isosurface/Isosurface.svelte +202 -148
  104. package/dist/isosurface/IsosurfaceControls.svelte +46 -28
  105. package/dist/isosurface/parse.js +34 -29
  106. package/dist/isosurface/slice.js +5 -10
  107. package/dist/isosurface/types.d.ts +2 -1
  108. package/dist/isosurface/types.js +61 -12
  109. package/dist/labels.js +11 -8
  110. package/dist/layout/FullscreenToggle.svelte +11 -2
  111. package/dist/layout/InfoCard.svelte +38 -6
  112. package/dist/layout/InfoTag.svelte +63 -32
  113. package/dist/layout/PropertyFilter.svelte +82 -37
  114. package/dist/layout/SettingsSection.svelte +85 -55
  115. package/dist/layout/SubpageGrid.svelte +10 -2
  116. package/dist/layout/json-tree/JsonNode.svelte +183 -138
  117. package/dist/layout/json-tree/JsonTree.svelte +499 -413
  118. package/dist/layout/json-tree/JsonValue.svelte +127 -99
  119. package/dist/layout/json-tree/utils.js +4 -2
  120. package/dist/marching-cubes.js +25 -2
  121. package/dist/math.d.ts +13 -17
  122. package/dist/math.js +133 -67
  123. package/dist/overlays/ContextMenu.svelte +65 -40
  124. package/dist/overlays/DraggablePane.svelte +211 -139
  125. package/dist/periodic-table/PeriodicTable.svelte +278 -145
  126. package/dist/periodic-table/PeriodicTableControls.svelte +178 -128
  127. package/dist/periodic-table/PropertySelect.svelte +25 -7
  128. package/dist/periodic-table/TableInset.svelte +8 -3
  129. package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte +446 -309
  130. package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte.d.ts +1 -1
  131. package/dist/phase-diagram/PhaseDiagramControls.svelte +102 -43
  132. package/dist/phase-diagram/PhaseDiagramControls.svelte.d.ts +1 -1
  133. package/dist/phase-diagram/PhaseDiagramEditorPane.svelte +63 -40
  134. package/dist/phase-diagram/PhaseDiagramExportPane.svelte +71 -28
  135. package/dist/phase-diagram/PhaseDiagramExportPane.svelte.d.ts +1 -1
  136. package/dist/phase-diagram/PhaseDiagramTooltip.svelte +158 -101
  137. package/dist/phase-diagram/TdbInfoPanel.svelte +28 -4
  138. package/dist/phase-diagram/build-diagram.js +9 -9
  139. package/dist/phase-diagram/colors.js +1 -3
  140. package/dist/phase-diagram/parse.js +10 -9
  141. package/dist/phase-diagram/svg-to-diagram.js +53 -49
  142. package/dist/phase-diagram/utils.d.ts +1 -0
  143. package/dist/phase-diagram/utils.js +80 -25
  144. package/dist/plot/AxisLabel.svelte +28 -3
  145. package/dist/plot/BarPlot.svelte +1182 -734
  146. package/dist/plot/BarPlot.svelte.d.ts +2 -2
  147. package/dist/plot/BarPlotControls.svelte +31 -5
  148. package/dist/plot/BarPlotControls.svelte.d.ts +1 -1
  149. package/dist/plot/ColorBar.svelte +479 -329
  150. package/dist/plot/ColorScaleSelect.svelte +27 -6
  151. package/dist/plot/ElementScatter.svelte +36 -15
  152. package/dist/plot/FillArea.svelte +152 -95
  153. package/dist/plot/Histogram.svelte +934 -571
  154. package/dist/plot/Histogram.svelte.d.ts +1 -1
  155. package/dist/plot/HistogramControls.svelte +53 -9
  156. package/dist/plot/HistogramControls.svelte.d.ts +1 -1
  157. package/dist/plot/InteractiveAxisLabel.svelte +34 -11
  158. package/dist/plot/InteractiveAxisLabel.svelte.d.ts +1 -1
  159. package/dist/plot/Line.svelte +63 -28
  160. package/dist/plot/PlotControls.svelte +157 -114
  161. package/dist/plot/PlotControls.svelte.d.ts +1 -1
  162. package/dist/plot/PlotLegend.svelte +174 -91
  163. package/dist/plot/PlotTooltip.svelte +45 -6
  164. package/dist/plot/PortalSelect.svelte +175 -147
  165. package/dist/plot/ReferenceLine.svelte +76 -22
  166. package/dist/plot/ReferenceLine3D.svelte +132 -107
  167. package/dist/plot/ReferencePlane.svelte +146 -121
  168. package/dist/plot/ScatterPlot.svelte +1681 -1091
  169. package/dist/plot/ScatterPlot.svelte.d.ts +2 -2
  170. package/dist/plot/ScatterPlot3D.svelte +256 -131
  171. package/dist/plot/ScatterPlot3D.svelte.d.ts +2 -2
  172. package/dist/plot/ScatterPlot3DControls.svelte +113 -63
  173. package/dist/plot/ScatterPlot3DControls.svelte.d.ts +2 -1
  174. package/dist/plot/ScatterPlot3DScene.svelte +608 -403
  175. package/dist/plot/ScatterPlot3DScene.svelte.d.ts +2 -2
  176. package/dist/plot/ScatterPlotControls.svelte +65 -25
  177. package/dist/plot/ScatterPlotControls.svelte.d.ts +1 -1
  178. package/dist/plot/ScatterPoint.svelte +98 -26
  179. package/dist/plot/ScatterPoint.svelte.d.ts +1 -0
  180. package/dist/plot/SpacegroupBarPlot.svelte +142 -85
  181. package/dist/plot/Surface3D.svelte +159 -108
  182. package/dist/plot/ZeroLines.svelte +55 -3
  183. package/dist/plot/ZoomRect.svelte +4 -2
  184. package/dist/plot/axis-utils.js +1 -3
  185. package/dist/plot/data-cleaning.js +12 -28
  186. package/dist/plot/data-transform.js +2 -1
  187. package/dist/plot/fill-utils.js +2 -0
  188. package/dist/plot/layout.d.ts +4 -1
  189. package/dist/plot/layout.js +33 -14
  190. package/dist/plot/reference-line.d.ts +2 -2
  191. package/dist/plot/reference-line.js +7 -5
  192. package/dist/plot/scales.js +24 -36
  193. package/dist/plot/types.d.ts +11 -23
  194. package/dist/plot/types.js +6 -11
  195. package/dist/plot/utils/label-placement.d.ts +32 -15
  196. package/dist/plot/utils/label-placement.js +227 -66
  197. package/dist/plot/utils/series-visibility.js +2 -3
  198. package/dist/rdf/RdfPlot.svelte +143 -91
  199. package/dist/rdf/calc-rdf.js +4 -5
  200. package/dist/sanitize.d.ts +4 -0
  201. package/dist/sanitize.js +107 -0
  202. package/dist/settings.d.ts +18 -6
  203. package/dist/settings.js +46 -16
  204. package/dist/spectral/Bands.svelte +632 -453
  205. package/dist/spectral/BandsAndDos.svelte +90 -49
  206. package/dist/spectral/BrillouinBandsDos.svelte +151 -93
  207. package/dist/spectral/Dos.svelte +389 -258
  208. package/dist/spectral/helpers.js +55 -43
  209. package/dist/state.svelte.d.ts +1 -1
  210. package/dist/state.svelte.js +3 -2
  211. package/dist/structure/Arrow.svelte +59 -20
  212. package/dist/structure/AtomLegend.svelte +215 -134
  213. package/dist/structure/Bond.svelte +73 -47
  214. package/dist/structure/CanvasTooltip.svelte +10 -2
  215. package/dist/structure/CellSelect.svelte +72 -45
  216. package/dist/structure/Cylinder.svelte +33 -17
  217. package/dist/structure/Lattice.svelte +88 -33
  218. package/dist/structure/Structure.svelte +1063 -797
  219. package/dist/structure/Structure.svelte.d.ts +1 -1
  220. package/dist/structure/StructureControls.svelte +349 -118
  221. package/dist/structure/StructureExportPane.svelte +124 -89
  222. package/dist/structure/StructureExportPane.svelte.d.ts +1 -1
  223. package/dist/structure/StructureInfoPane.svelte +304 -237
  224. package/dist/structure/StructureScene.svelte +879 -443
  225. package/dist/structure/StructureScene.svelte.d.ts +15 -7
  226. package/dist/structure/atom-properties.js +8 -8
  227. package/dist/structure/bonding.js +6 -7
  228. package/dist/structure/export.js +14 -29
  229. package/dist/structure/ferrox-wasm.js +1 -1
  230. package/dist/structure/index.d.ts +13 -3
  231. package/dist/structure/index.js +83 -23
  232. package/dist/structure/measure.d.ts +2 -2
  233. package/dist/structure/measure.js +4 -44
  234. package/dist/structure/parse.js +113 -141
  235. package/dist/structure/partial-occupancy.js +7 -10
  236. package/dist/structure/pbc.d.ts +1 -0
  237. package/dist/structure/pbc.js +16 -6
  238. package/dist/structure/supercell.d.ts +2 -2
  239. package/dist/structure/supercell.js +12 -22
  240. package/dist/structure/validation.js +1 -2
  241. package/dist/symmetry/SymmetryStats.svelte +84 -41
  242. package/dist/symmetry/WyckoffTable.svelte +26 -6
  243. package/dist/symmetry/cell-transform.js +5 -3
  244. package/dist/symmetry/index.js +8 -7
  245. package/dist/symmetry/spacegroups.js +148 -148
  246. package/dist/table/HeatmapTable.svelte +790 -554
  247. package/dist/table/HeatmapTable.svelte.d.ts +1 -1
  248. package/dist/table/ToggleMenu.svelte +125 -92
  249. package/dist/table/index.js +2 -4
  250. package/dist/theme/ThemeControl.svelte +21 -12
  251. package/dist/time.js +4 -1
  252. package/dist/tooltip/TooltipContent.svelte +33 -8
  253. package/dist/trajectory/Trajectory.svelte +758 -558
  254. package/dist/trajectory/TrajectoryError.svelte +14 -3
  255. package/dist/trajectory/TrajectoryExportPane.svelte +137 -83
  256. package/dist/trajectory/TrajectoryInfoPane.svelte +272 -143
  257. package/dist/trajectory/extract.js +10 -26
  258. package/dist/trajectory/format-detect.js +5 -5
  259. package/dist/trajectory/frame-reader.d.ts +1 -1
  260. package/dist/trajectory/frame-reader.js +5 -12
  261. package/dist/trajectory/helpers.d.ts +0 -1
  262. package/dist/trajectory/helpers.js +2 -17
  263. package/dist/trajectory/index.js +14 -12
  264. package/dist/trajectory/parse/ase.js +5 -4
  265. package/dist/trajectory/parse/hdf5.js +26 -18
  266. package/dist/trajectory/parse/index.js +13 -18
  267. package/dist/trajectory/parse/lammps.js +17 -7
  268. package/dist/trajectory/parse/vasp.js +5 -2
  269. package/dist/trajectory/parse/xyz.js +8 -7
  270. package/dist/trajectory/plotting.js +13 -8
  271. package/dist/utils.d.ts +1 -0
  272. package/dist/utils.js +13 -0
  273. package/dist/xrd/XrdPlot.svelte +337 -247
  274. package/dist/xrd/broadening.js +14 -9
  275. package/dist/xrd/calc-xrd.js +12 -18
  276. package/dist/xrd/parse.d.ts +1 -1
  277. package/dist/xrd/parse.js +17 -17
  278. package/package.json +99 -103
  279. package/readme.md +1 -1
  280. /package/dist/theme/{themes.js → themes.mjs} +0 -0
@@ -13,6 +13,7 @@ export const CHEMPOT_DEFAULTS = {
13
13
  reverse_color_scale: true,
14
14
  interpolate_temperature: true,
15
15
  max_interpolation_gap: 500,
16
+ projection_mode: `single`,
16
17
  // Dark2 qualitative palette (same as pymatgen/plotly default)
17
18
  formula_colors: [
18
19
  `#1b9e77`,
@@ -3,7 +3,7 @@ import type { Vec3 } from '../math';
3
3
  import type { ELEM_SYMBOLS } from '../labels';
4
4
  export type D3InterpolateName = keyof typeof d3_sc & `interpolate${string}`;
5
5
  export type D3ColorSchemeName = D3InterpolateName extends `interpolate${infer Name}` ? Name : never;
6
- export declare const get_d3_interpolator: (name: D3InterpolateName) => (t: number) => string;
6
+ export declare const get_d3_interpolator: (name: D3InterpolateName) => ((t: number) => string);
7
7
  export declare const COLOR_SCALE_TYPES: readonly ["continuous", "categorical"];
8
8
  export type ColorScaleType = (typeof COLOR_SCALE_TYPES)[number];
9
9
  export declare const DEFAULT_CATEGORY_COLORS: Record<string, string>;
@@ -61,10 +61,10 @@ export const is_color = (val) => {
61
61
  return false;
62
62
  // Check for hex colors, rgb/rgba, hsl/hsla, color(), var(), and named colors
63
63
  // Exclude incomplete function prefixes like 'rgb', 'hsl', 'var', 'color'
64
- return /^(#[0-9a-f]{3,8}|rgba?\([^)]+\)|hsla?\([^)]+\)|color\([^)]+\)|var\([^)]+\)|(?!rgb$|hsl$|var$|color$)[a-z]+)$/i
65
- .test(val.toString().trim());
64
+ return /^(#[0-9a-f]{3,8}|rgba?\([^)]+\)|hsla?\([^)]+\)|color\([^)]+\)|var\([^)]+\)|(?!rgb$|hsl$|var$|color$)[a-z]+)$/i.test(val.trim());
66
65
  };
67
66
  export const PLOT_COLORS = [
67
+ // Color series for e.g. line plots
68
68
  `#63b3ed`,
69
69
  `#68d391`,
70
70
  `#fbd38d`,
@@ -133,7 +133,9 @@ export function is_dark_mode() {
133
133
  if (stored === `dark` || stored === `light`)
134
134
  return stored === `dark`;
135
135
  }
136
- catch { /* localStorage throws in private browsing mode */ }
136
+ catch {
137
+ /* localStorage throws in private browsing mode */
138
+ }
137
139
  return globalThis.matchMedia?.(`(prefers-color-scheme: dark)`).matches ?? false;
138
140
  }
139
141
  // Watch for dark mode changes and call callback on each change. Returns cleanup function.
@@ -1,59 +1,132 @@
1
- <script lang="ts">import { ELEMENT_COLOR_SCHEMES, pick_contrast_color } from '../colors';
2
- import { format_num } from '../labels';
3
- import { get_chart_font_scale } from './index';
4
- import { fractional_composition } from './parse';
5
- let { composition, size = 200, bar_height = 30, label_height = 20, gap = 2, min_segment_size_for_label = 15, thin_segment_threshold = 0.2, external_label_size_threshold = 5, outer_corners_only = true, show_labels = true, show_percentages = false, show_amounts = true, color_scheme = `Vesta`, segment_content, interactive = true, svg_node = $bindable(null), children, ...rest } = $props();
6
- let element_colors = $derived(ELEMENT_COLOR_SCHEMES[color_scheme] || ELEMENT_COLOR_SCHEMES.Vesta);
7
- let fractions = $derived(fractional_composition(composition));
8
- let svg_height = $derived(label_height + gap + bar_height + gap + label_height);
9
- let bar_y = $derived(label_height + gap);
10
- let above_labels_y = $derived(label_height / 2);
11
- let below_labels_y = $derived(label_height + gap + bar_height + gap + label_height / 2);
12
- let segments = $derived.by(() => {
13
- const element_entries = Object.entries(composition).filter(([_, amount]) => amount && amount > 0);
14
- if (element_entries.length === 0)
15
- return [];
16
- let [current_x, above_labels, below_labels] = [0, 0, 0];
1
+ <script lang="ts">
2
+ import type { ColorSchemeName } from '../colors'
3
+ import { ELEMENT_COLOR_SCHEMES, pick_contrast_color } from '../colors'
4
+ import type { CompositionType } from './'
5
+ import type { ElementSymbol } from '../element'
6
+ import { format_num } from '../labels'
7
+ import type { Snippet } from 'svelte'
8
+ import type { SVGAttributes } from 'svelte/elements'
9
+ import { type ChartSegmentData, get_chart_font_scale } from './index'
10
+ import { fractional_composition } from './parse'
11
+
12
+ type BarSegmentData = ChartSegmentData & {
13
+ x: number
14
+ width: number
15
+ can_show_label: boolean
16
+ needs_external_label: boolean
17
+ external_label_position: `above` | `below` | null
18
+ label_x: number
19
+ label_y: number
20
+ }
21
+
22
+ let {
23
+ composition,
24
+ size = 200,
25
+ bar_height = 30,
26
+ label_height = 20,
27
+ gap = 2,
28
+ min_segment_size_for_label = 15,
29
+ thin_segment_threshold = 0.2,
30
+ external_label_size_threshold = 5,
31
+ outer_corners_only = true,
32
+ show_labels = true,
33
+ show_percentages = false,
34
+ show_amounts = true,
35
+ color_scheme = `Vesta`,
36
+ segment_content,
37
+ interactive = true,
38
+ svg_node = $bindable(null),
39
+ children,
40
+ ...rest
41
+ }: SVGAttributes<SVGSVGElement> & {
42
+ composition: CompositionType
43
+ size?: number
44
+ bar_height?: number
45
+ label_height?: number
46
+ gap?: number
47
+ min_segment_size_for_label?: number
48
+ thin_segment_threshold?: number
49
+ external_label_size_threshold?: number
50
+ outer_corners_only?: boolean
51
+ show_labels?: boolean
52
+ show_percentages?: boolean
53
+ show_amounts?: boolean
54
+ color_scheme?: ColorSchemeName
55
+ segment_content?: Snippet<[BarSegmentData]>
56
+ interactive?: boolean
57
+ svg_node?: SVGSVGElement | null
58
+ children?: Snippet<[{ hovered_element: ElementSymbol | null }]>
59
+ } = $props()
60
+
61
+ let element_colors = $derived(
62
+ ELEMENT_COLOR_SCHEMES[color_scheme] || ELEMENT_COLOR_SCHEMES.Vesta,
63
+ )
64
+ let fractions = $derived(fractional_composition(composition))
65
+
66
+ let svg_height = $derived(label_height + gap + bar_height + gap + label_height)
67
+ let bar_y = $derived(label_height + gap)
68
+ let above_labels_y = $derived(label_height / 2)
69
+ let below_labels_y = $derived(
70
+ label_height + gap + bar_height + gap + label_height / 2,
71
+ )
72
+
73
+ let segments = $derived.by(() => {
74
+ const element_entries = Object.entries(composition).filter(([_, amount]) =>
75
+ amount && amount > 0
76
+ ) as [ElementSymbol, number][]
77
+ if (element_entries.length === 0) return []
78
+
79
+ let [current_x, above_labels, below_labels] = [0, 0, 0]
80
+
17
81
  return element_entries.map(([element, amount]) => {
18
- const fraction = fractions[element] || 0;
19
- const color = element_colors[element] || `#cccccc`;
20
- const width = fraction * size;
21
- const x = current_x;
22
- current_x += width;
23
- const segment_size = Math.min(width, size);
24
- const base_scale = Math.min(2, Math.max(1, segment_size / 40));
25
- const label_text = element + (show_amounts ? amount.toString() : ``) +
26
- (show_percentages ? `${format_num(fraction, `.1~%`)}` : ``);
27
- const font_scale = get_chart_font_scale(base_scale, label_text, segment_size * 0.9, 0.6, 12);
28
- // Label positioning
29
- const can_show_label = segment_size >= min_segment_size_for_label;
30
- const is_thin = fraction < thin_segment_threshold;
31
- const can_show_external_label = segment_size >= external_label_size_threshold;
32
- const needs_external_label = is_thin && can_show_external_label;
33
- let external_label_position = null;
34
- if (needs_external_label) {
35
- external_label_position = above_labels <= below_labels ? `above` : `below`;
36
- if (external_label_position === `above`)
37
- above_labels++;
38
- else
39
- below_labels++;
40
- }
41
- const text_color = pick_contrast_color({ bg_color: color });
42
- const label_props = {
43
- font_scale,
44
- text_color,
45
- can_show_label,
46
- needs_external_label,
47
- external_label_position,
48
- label_x: x + width / 2,
49
- label_y: bar_y + bar_height / 2,
50
- };
51
- return { element, amount, fraction, color, x, width, ...label_props };
52
- });
53
- });
54
- let hovered_element = $state(null);
55
- // Generate unique ID for clipPath to avoid collisions across BarCharts
56
- let clip_path_id = $derived(`bar-clip-${crypto.randomUUID()}`);
82
+ const fraction = fractions[element] || 0
83
+ const color = element_colors[element] || `#cccccc`
84
+ const width = fraction * size
85
+ const x = current_x
86
+ current_x += width
87
+
88
+ const segment_size = Math.min(width, size)
89
+ const base_scale = Math.min(2, Math.max(1, segment_size / 40))
90
+ const label_text = element + (show_amounts ? amount?.toString() ?? `` : ``) +
91
+ (show_percentages ? `${format_num(fraction, `.1~%`)}` : ``)
92
+ const font_scale = get_chart_font_scale(
93
+ base_scale,
94
+ label_text,
95
+ segment_size * 0.9,
96
+ 0.6,
97
+ 12,
98
+ )
99
+
100
+ // Label positioning
101
+ const can_show_label = segment_size >= min_segment_size_for_label
102
+ const is_thin = fraction < thin_segment_threshold
103
+ const can_show_external_label = segment_size >= external_label_size_threshold
104
+ const needs_external_label = is_thin && can_show_external_label
105
+
106
+ let external_label_position: `above` | `below` | null = null
107
+ if (needs_external_label) {
108
+ external_label_position = above_labels <= below_labels ? `above` : `below`
109
+ if (external_label_position === `above`) above_labels++
110
+ else below_labels++
111
+ }
112
+
113
+ const text_color = pick_contrast_color({ bg_color: color })
114
+ const label_props = {
115
+ font_scale,
116
+ text_color,
117
+ can_show_label,
118
+ needs_external_label,
119
+ external_label_position,
120
+ label_x: x + width / 2,
121
+ label_y: bar_y + bar_height / 2,
122
+ }
123
+ return { element, amount, fraction, color, x, width, ...label_props }
124
+ })
125
+ })
126
+
127
+ let hovered_element: ElementSymbol | null = $state(null)
128
+ // Generate unique ID for clipPath to avoid collisions across BarCharts
129
+ let clip_path_id = $derived(`bar-clip-${crypto.randomUUID()}`)
57
130
  </script>
58
131
 
59
132
  {#snippet label_content(segment: BarSegmentData)}
@@ -1,55 +1,108 @@
1
- <script lang="ts">import { ELEMENT_COLOR_SCHEMES, pick_contrast_color } from '../colors';
2
- import { hierarchy, pack } from 'd3-hierarchy';
3
- import { get_chart_font_scale } from './index';
4
- import { count_atoms_in_composition } from './parse';
5
- let { composition, size = 200, padding = 0, show_labels = true, show_amounts = true, color_scheme = `Vesta`, bubble_content, interactive = true, svg_node = $bindable(null), children, ...rest } = $props();
6
- let element_colors = $derived(ELEMENT_COLOR_SCHEMES[color_scheme] || ELEMENT_COLOR_SCHEMES.Vesta);
7
- // Calculate bubble data with proper circle packing
8
- let bubbles = $derived.by(() => {
9
- const element_entries = Object.entries(composition).filter(([_, amount]) => amount && amount > 0);
10
- if (element_entries.length === 0)
11
- return [];
12
- const hierarchy_data = {
13
- children: element_entries.map(([element, amount]) => ({
14
- element,
15
- amount: amount ?? 0,
16
- color: element_colors[element] || `#cccccc`,
17
- })),
18
- };
1
+ <script lang="ts">
2
+ import type { ColorSchemeName } from '../colors'
3
+ import { ELEMENT_COLOR_SCHEMES, pick_contrast_color } from '../colors'
4
+ import type { CompositionType } from './'
5
+ import type { ElementSymbol } from '../element'
6
+ import { hierarchy, pack } from 'd3-hierarchy'
7
+ import type { Snippet } from 'svelte'
8
+ import type { SVGAttributes } from 'svelte/elements'
9
+ import { type ChartSegmentData, get_chart_font_scale } from './index'
10
+ import { count_atoms_in_composition } from './parse'
11
+
12
+ type BubbleSegmentData = ChartSegmentData & { radius: number; x: number; y: number }
13
+
14
+ let {
15
+ composition,
16
+ size = 200,
17
+ padding = 0,
18
+ show_labels = true,
19
+ show_amounts = true,
20
+ color_scheme = `Vesta`,
21
+ bubble_content,
22
+ interactive = true,
23
+ svg_node = $bindable(null),
24
+ children,
25
+ ...rest
26
+ }: SVGAttributes<SVGSVGElement> & {
27
+ composition: CompositionType
28
+ size?: number
29
+ padding?: number
30
+ show_labels?: boolean
31
+ show_amounts?: boolean
32
+ color_scheme?: ColorSchemeName
33
+ bubble_content?: Snippet<[BubbleSegmentData]>
34
+ interactive?: boolean
35
+ svg_node?: SVGSVGElement | null
36
+ children?: Snippet<[{ hovered_element: ElementSymbol | null }]>
37
+ } = $props()
38
+
39
+ let element_colors = $derived(
40
+ ELEMENT_COLOR_SCHEMES[color_scheme] || ELEMENT_COLOR_SCHEMES.Vesta,
41
+ )
42
+
43
+ // Calculate bubble data with proper circle packing
44
+ let bubbles = $derived.by<BubbleSegmentData[]>(() => {
45
+ const element_entries = Object.entries(composition).filter(
46
+ ([_, amount]) => amount && amount > 0,
47
+ )
48
+ if (element_entries.length === 0) return []
49
+
50
+ // Create hierarchy data structure for D3 pack
51
+ type Child = { element: string; amount: number; color: string }
52
+ type HierarchyData = { children: Child[] }
53
+
54
+ const hierarchy_data: HierarchyData = {
55
+ children: element_entries.map(([element, amount]) => ({
56
+ element,
57
+ amount: amount ?? 0,
58
+ color: element_colors[element] || `#cccccc`,
59
+ })),
60
+ }
61
+
19
62
  // Use D3's pack layout for proper circle packing
20
- const pack_layout = pack().size([
21
- size - 2 * padding,
22
- size - 2 * padding,
23
- ]).padding(padding * 0.1); // Small padding between circles
24
- const root = pack_layout(hierarchy(hierarchy_data).sum((data) => (`amount` in data ? data.amount : 0)));
63
+ const pack_layout = pack<HierarchyData | Child>().size([
64
+ size - 2 * padding,
65
+ size - 2 * padding,
66
+ ]).padding(padding * 0.1) // Small padding between circles
67
+
68
+ const root = pack_layout(
69
+ hierarchy<HierarchyData | Child>(hierarchy_data).sum(
70
+ (data) => (`amount` in data ? data.amount : 0),
71
+ ),
72
+ )
73
+
25
74
  // Get max radius for font scaling
26
- const max_radius = Math.max(...root.leaves().map((data) => data.r || 0));
27
- const total_atoms = count_atoms_in_composition(composition);
75
+ const max_radius = Math.max(...root.leaves().map((data) => data.r || 0))
76
+ const total_atoms = count_atoms_in_composition(composition)
77
+
28
78
  return root.leaves().map((node) => {
29
- const radius = node.r || 0;
30
- const data = node.data;
31
- // Calculate font scale based on bubble size and smart text fitting
32
- const [min_font_scale, max_font_scale] = [0.6, 2];
33
- const scale_factor = radius / max_radius;
34
- const base_scale = min_font_scale +
35
- scale_factor * (max_font_scale - min_font_scale);
36
- const label_text = data.element + (show_amounts ? data.amount.toString() : ``);
37
- const available_space = radius * 2 * 0.8; // 80% of bubble diameter for text
38
- const font_scale = get_chart_font_scale(base_scale, label_text, available_space);
39
- return {
40
- element: data.element,
41
- amount: data.amount,
42
- fraction: total_atoms > 0 ? data.amount / total_atoms : 0,
43
- radius,
44
- x: (node.x || 0) + padding, // Offset by padding
45
- y: (node.y || 0) + padding,
46
- color: data.color,
47
- font_scale,
48
- text_color: pick_contrast_color({ bg_color: data.color }),
49
- };
50
- });
51
- });
52
- let hovered_element = $state(null);
79
+ const radius = node.r || 0
80
+ const data = node.data as Child
81
+
82
+ // Calculate font scale based on bubble size and smart text fitting
83
+ const [min_font_scale, max_font_scale] = [0.6, 2] as const
84
+ const scale_factor = radius / max_radius
85
+ const base_scale = min_font_scale +
86
+ scale_factor * (max_font_scale - min_font_scale)
87
+ const label_text = data.element + (show_amounts ? data.amount.toString() : ``)
88
+ const available_space = radius * 2 * 0.8 // 80% of bubble diameter for text
89
+ const font_scale = get_chart_font_scale(base_scale, label_text, available_space)
90
+
91
+ return {
92
+ element: data.element as ElementSymbol,
93
+ amount: data.amount,
94
+ fraction: total_atoms > 0 ? data.amount / total_atoms : 0,
95
+ radius,
96
+ x: (node.x || 0) + padding, // Offset by padding
97
+ y: (node.y || 0) + padding,
98
+ color: data.color,
99
+ font_scale,
100
+ text_color: pick_contrast_color({ bg_color: data.color }),
101
+ }
102
+ })
103
+ })
104
+
105
+ let hovered_element: ElementSymbol | null = $state(null)
53
106
  </script>
54
107
 
55
108
  <svg
@@ -1,107 +1,128 @@
1
- <script lang="ts">import { untrack } from 'svelte';
2
- import { ContextMenu } from '../overlays';
3
- import { export_svg_as_png, export_svg_as_svg } from '../io/export';
4
- import { get_electro_neg_formula } from './format';
5
- import { BarChart, BubbleChart, PieChart } from './index';
6
- import { parse_composition } from './parse';
7
- let { composition, mode = `pie`, on_composition_change, color_scheme = `Vesta`, ...rest } = $props();
8
- // Using $state with untrack() - initialized from props but mutated by context menu
9
- let current_color_scheme = $state(untrack(() => color_scheme));
10
- let current_mode = $state(untrack(() => mode));
11
- let svg_node = $state(null);
12
- let Component = $derived({ pie: PieChart, bubble: BubbleChart, bar: BarChart }[current_mode]);
13
- let parsed = $derived.by(() => {
1
+ <script lang="ts">
2
+ import type { ColorSchemeName } from '../colors'
3
+ import type { CompositionType } from './'
4
+ import { untrack } from 'svelte'
5
+ import { ContextMenu } from '../overlays'
6
+ import { export_svg_as_png, export_svg_as_svg } from '../io/export'
7
+ import type { SVGAttributes } from 'svelte/elements'
8
+ import { get_electro_neg_formula } from './format'
9
+ import { BarChart, BubbleChart, PieChart } from './index'
10
+ import { parse_composition } from './parse'
11
+
12
+ type CompositionChartMode = `pie` | `bubble` | `bar`
13
+ let {
14
+ composition,
15
+ mode = `pie`,
16
+ on_composition_change,
17
+ color_scheme = `Vesta`,
18
+ ...rest
19
+ }: SVGAttributes<SVGSVGElement> & {
20
+ composition: string | CompositionType
21
+ mode?: CompositionChartMode
22
+ on_composition_change?: (composition: CompositionType) => void
23
+ color_scheme?: ColorSchemeName
24
+ size?: number
25
+ interactive?: boolean
26
+ } = $props()
27
+
28
+ // Using $state with untrack() - initialized from props but mutated by context menu
29
+ let current_color_scheme = $state(untrack(() => color_scheme as ColorSchemeName))
30
+ let current_mode = $state(untrack(() => mode))
31
+ let svg_node = $state<SVGSVGElement | null>(null)
32
+
33
+ let Component = $derived(
34
+ { pie: PieChart, bubble: BubbleChart, bar: BarChart }[current_mode],
35
+ )
36
+ let parsed: CompositionType = $derived.by(() => {
14
37
  try {
15
- return parse_composition(composition);
16
- }
17
- catch (error) {
18
- console.error(`Failed to parse composition:`, error);
19
- return {};
38
+ return parse_composition(composition)
39
+ } catch (error) {
40
+ console.error(`Failed to parse composition:`, error)
41
+ return {}
20
42
  }
21
- });
22
- // Call the composition change callback in an effect, not in the derived
23
- $effect(() => on_composition_change?.(parsed));
24
- let context_menu = $state({ open: false, x: 0, y: 0 });
25
- function handle_right_click(event) {
26
- event.preventDefault();
27
- context_menu.open = false; // Close any existing context menu first
28
- context_menu.x = event.pageX;
29
- context_menu.y = event.pageY;
43
+ })
44
+ // Call the composition change callback in an effect, not in the derived
45
+ $effect(() => on_composition_change?.(parsed))
46
+
47
+ let context_menu = $state({ open: false, x: 0, y: 0 })
48
+
49
+ function handle_right_click(event: MouseEvent) { // open context menu
50
+ event.preventDefault()
51
+ context_menu.open = false // Close any existing context menu first
52
+ context_menu.x = event.pageX
53
+ context_menu.y = event.pageY
30
54
  // Use a small delay to ensure the prev context menu closes happens before opening new one
31
- setTimeout(() => context_menu.open = true, 0);
32
- }
33
- const mode_options = [
55
+ setTimeout(() => context_menu.open = true, 0)
56
+ }
57
+
58
+ const mode_options = [
34
59
  { value: `pie`, icon: `Circle`, label: `Pie Chart` },
35
60
  { value: `bubble`, icon: `Circle`, label: `Bubble Chart` },
36
61
  { value: `bar`, icon: `Graph`, label: `Bar Chart` },
37
- ];
38
- const color_scheme_options = [
62
+ ] as const
63
+
64
+ const color_scheme_options = [
39
65
  { value: `Vesta`, icon: `ColorPalette`, label: `Vesta` },
40
66
  { value: `Jmol`, icon: `ColorPalette`, label: `Jmol` },
41
67
  { value: `Alloy`, icon: `ColorPalette`, label: `Alloy` },
42
68
  { value: `Pastel`, icon: `ColorPalette`, label: `Pastel` },
43
69
  { value: `Muted`, icon: `ColorPalette`, label: `Muted` },
44
70
  { value: `Dark Mode`, icon: `ColorPalette`, label: `Dark Mode` },
45
- ];
46
- const export_options = [
71
+ ] as const
72
+
73
+ const export_options = [
47
74
  { value: `copy_formula`, icon: `Copy`, label: `Copy Formula` },
48
75
  { value: `copy_data`, icon: `Copy`, label: `Copy Data` },
49
76
  { value: `export_svg`, icon: `Download`, label: `Export SVG` },
50
77
  { value: `export_png`, icon: `Download`, label: `Export PNG` },
51
- ];
52
- const sec_titles = {
78
+ ] as const
79
+
80
+ const sec_titles = {
53
81
  display_mode: `Display Mode`,
54
82
  color_scheme: `Color Scheme`,
55
83
  export: `Export`,
56
- };
57
- const context_menu_sections = [
84
+ } as const
85
+
86
+ const context_menu_sections = [
58
87
  { title: sec_titles.display_mode, options: mode_options },
59
88
  { title: sec_titles.color_scheme, options: color_scheme_options },
60
89
  { title: sec_titles.export, options: export_options },
61
- ];
62
- function handle_context_menu_select(section_title, option) {
90
+ ] as const
91
+
92
+ function handle_context_menu_select(
93
+ section_title: string,
94
+ option: { value: string },
95
+ ) {
63
96
  if (section_title === sec_titles.display_mode) {
64
- current_mode = option.value;
65
- }
66
- else if (section_title === sec_titles.color_scheme) {
67
- current_color_scheme = option.value;
68
- }
69
- else if (section_title === sec_titles.export)
70
- handle_export(option.value);
71
- context_menu.open = false;
72
- }
73
- // Handle export actions
74
- function handle_export(export_type) {
97
+ current_mode = option.value as CompositionChartMode
98
+ } else if (section_title === sec_titles.color_scheme) {
99
+ current_color_scheme = option.value as ColorSchemeName
100
+ } else if (section_title === sec_titles.export) handle_export(option.value)
101
+ context_menu.open = false
102
+ }
103
+
104
+ // Handle export actions
105
+ function handle_export(export_type: string) {
75
106
  try {
76
- if (export_type === `copy_formula`) {
77
- const formula = get_electro_neg_formula(composition);
78
- navigator.clipboard.writeText(formula);
79
- }
80
- else if (export_type === `copy_data`) {
81
- const data = JSON.stringify(parsed, null, 2);
82
- navigator.clipboard.writeText(data);
83
- }
84
- else if (export_type === `export_svg`) {
85
- const filename = `${get_electro_neg_formula(composition, true, ``)}.svg`;
86
- if (svg_node)
87
- export_svg_as_svg(svg_node, filename);
88
- else
89
- console.warn(`Chart SVG not available for SVG export`);
90
- }
91
- else if (export_type === `export_png`) {
92
- const filename = `${get_electro_neg_formula(composition, true, ``)}.png`;
93
- if (svg_node)
94
- export_svg_as_png(svg_node, filename, 150);
95
- else
96
- console.warn(`Chart SVG not available for PNG export`);
97
- }
98
- else
99
- console.warn(`Invalid export type:`, export_type);
100
- }
101
- catch (error) {
102
- console.error(`Export failed:`, error);
107
+ if (export_type === `copy_formula`) {
108
+ const formula = get_electro_neg_formula(composition)
109
+ navigator.clipboard.writeText(formula)
110
+ } else if (export_type === `copy_data`) {
111
+ const data = JSON.stringify(parsed, null, 2)
112
+ navigator.clipboard.writeText(data)
113
+ } else if (export_type === `export_svg`) {
114
+ const filename = `${get_electro_neg_formula(composition, true, ``)}.svg`
115
+ if (svg_node) export_svg_as_svg(svg_node, filename)
116
+ else console.warn(`Chart SVG not available for SVG export`)
117
+ } else if (export_type === `export_png`) {
118
+ const filename = `${get_electro_neg_formula(composition, true, ``)}.png`
119
+ if (svg_node) export_svg_as_png(svg_node, filename, 150)
120
+ else console.warn(`Chart SVG not available for PNG export`)
121
+ } else console.warn(`Invalid export type:`, export_type)
122
+ } catch (error) {
123
+ console.error(`Export failed:`, error)
103
124
  }
104
- }
125
+ }
105
126
  </script>
106
127
 
107
128
  <Component