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
@@ -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