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
@@ -125,10 +125,10 @@ function extract_mpds_scales(doc) {
125
125
  // This is intentional — only endpoints are used for scale mapping.
126
126
  const comp_vals = [
127
127
  ...new Set(numbers.filter((v) => v >= 0 && v <= 100 && v % 10 === 0)),
128
- ].sort((a, b) => a - b);
128
+ ].toSorted((a, b) => a - b);
129
129
  const temp_vals = [
130
130
  ...new Set(numbers.filter((v) => v >= 100 && v % 100 === 0 && v <= 3000)),
131
- ].sort((a, b) => a - b);
131
+ ].toSorted((a, b) => a - b);
132
132
  if (comp_vals.length < 2 || temp_vals.length < 2) {
133
133
  throw new Error(`MPDS SVG: could not infer axis ranges (found ${comp_vals.length} composition, ${temp_vals.length} temperature values)`);
134
134
  }
@@ -137,13 +137,13 @@ function extract_mpds_scales(doc) {
137
137
  const x_major_ticks = [];
138
138
  const y_major_ticks = [];
139
139
  for (const path of Array.from(doc.querySelectorAll(`path`))) {
140
- const d = path.getAttribute(`d`) ?? ``;
140
+ const path_data = path.getAttribute(`d`) ?? ``;
141
141
  const stroke_width = parse_stroke_width(path);
142
142
  // Tick mark paths have stroke-width ~0.5
143
143
  if (stroke_width < 0.3 || stroke_width > 1.0)
144
144
  continue;
145
145
  // Parse path into absolute line segments (handles both absolute & relative commands)
146
- const segments = parse_path_segments(d);
146
+ const segments = parse_path_segments(path_data);
147
147
  if (segments.length < 3)
148
148
  continue;
149
149
  for (const [sx1, sy1, sx2, sy2] of segments) {
@@ -163,10 +163,12 @@ function extract_mpds_scales(doc) {
163
163
  }
164
164
  }
165
165
  // Deduplicate and sort
166
- const x_ticks_sorted = [...new Set(x_major_ticks.map((v) => Math.round(v * 10) / 10))]
167
- .sort((a, b) => a - b);
168
- const y_ticks_sorted = [...new Set(y_major_ticks.map((v) => Math.round(v * 10) / 10))]
169
- .sort((a, b) => a - b);
166
+ const x_ticks_sorted = [
167
+ ...new Set(x_major_ticks.map((v) => Math.round(v * 10) / 10)),
168
+ ].toSorted((a, b) => a - b);
169
+ const y_ticks_sorted = [
170
+ ...new Set(y_major_ticks.map((v) => Math.round(v * 10) / 10)),
171
+ ].toSorted((a, b) => a - b);
170
172
  if (x_ticks_sorted.length < 2 || y_ticks_sorted.length < 2) {
171
173
  throw new Error(`MPDS SVG: could not find tick marks (found ${x_ticks_sorted.length} x-ticks, ${y_ticks_sorted.length} y-ticks)`);
172
174
  }
@@ -265,29 +267,34 @@ function extract_mpds_boundaries(doc, boundaries, x_scale, y_scale, epsilon) {
265
267
  return;
266
268
  const { left, right, top, bottom } = plot_rect;
267
269
  for (const path of Array.from(doc.querySelectorAll(`path`))) {
268
- const d = path.getAttribute(`d`) ?? ``;
270
+ const path_data = path.getAttribute(`d`) ?? ``;
269
271
  const style = path.getAttribute(`style`) ?? ``;
270
272
  // Skip tick mark paths (stroke-width > 0.3)
271
273
  const stroke_width = parse_stroke_width(path);
272
274
  if (stroke_width > 0.3 || stroke_width === 0)
273
275
  continue;
274
276
  // Skip filled regions (phase region fills)
275
- if (style.includes(`fill-rule`) || style.includes(`fill: #`) || style.includes(`fill:#`)) {
276
- if (!style.includes(`fill: none`) && !style.includes(`fill:none`))
277
- continue;
277
+ if ((style.includes(`fill-rule`) || style.includes(`fill: #`) || style.includes(`fill:#`)) &&
278
+ !style.includes(`fill: none`) &&
279
+ !style.includes(`fill:none`)) {
280
+ continue;
278
281
  }
279
282
  // Skip red annotation lines
280
283
  if (style.includes(`#e30016`) || style.includes(`#E30016`))
281
284
  continue;
282
285
  // Parse as simple M...L line
283
- const coords = parse_ml_path(d);
286
+ const coords = parse_ml_path(path_data);
284
287
  if (!coords)
285
288
  continue;
286
289
  // Must be inside the plot area
287
- const inside = coords.x1 >= left - 1 && coords.x1 <= right + 1 &&
288
- coords.x2 >= left - 1 && coords.x2 <= right + 1 &&
289
- coords.y1 >= top - 1 && coords.y1 <= bottom + 1 &&
290
- coords.y2 >= top - 1 && coords.y2 <= bottom + 1;
290
+ const inside = coords.x1 >= left - 1 &&
291
+ coords.x1 <= right + 1 &&
292
+ coords.x2 >= left - 1 &&
293
+ coords.x2 <= right + 1 &&
294
+ coords.y1 >= top - 1 &&
295
+ coords.y1 <= bottom + 1 &&
296
+ coords.y2 >= top - 1 &&
297
+ coords.y2 <= bottom + 1;
291
298
  if (!inside)
292
299
  continue;
293
300
  // Skip very short segments (< 10px)
@@ -297,8 +304,7 @@ function extract_mpds_boundaries(doc, boundaries, x_scale, y_scale, epsilon) {
297
304
  continue;
298
305
  // Skip the plot border itself (connects all 4 edges)
299
306
  const touches_left = Math.abs(coords.x1 - left) < 2 || Math.abs(coords.x2 - left) < 2;
300
- const touches_right = Math.abs(coords.x1 - right) < 2 ||
301
- Math.abs(coords.x2 - right) < 2;
307
+ const touches_right = Math.abs(coords.x1 - right) < 2 || Math.abs(coords.x2 - right) < 2;
302
308
  if (touches_left && touches_right)
303
309
  continue; // spans full width = likely axis
304
310
  add_boundary(boundaries, coords, x_scale, y_scale, epsilon);
@@ -308,14 +314,14 @@ function extract_mpds_boundaries(doc, boundaries, x_scale, y_scale, epsilon) {
308
314
  // Handles both M...L...L...L...Z and M...V...H...V...Z formats
309
315
  function find_mpds_plot_rect(doc) {
310
316
  for (const path of Array.from(doc.querySelectorAll(`path`))) {
311
- const d = path.getAttribute(`d`) ?? ``;
317
+ const path_data = path.getAttribute(`d`) ?? ``;
312
318
  const style = path.getAttribute(`style`) ?? ``;
313
319
  if (!style.includes(`fill: none`) && !style.includes(`fill:none`))
314
320
  continue;
315
- if (!d.includes(`Z`) && !d.includes(`z`))
321
+ if (!path_data.includes(`Z`) && !path_data.includes(`z`))
316
322
  continue;
317
323
  // Parse path into absolute segments and extract corner points
318
- const segments = parse_path_segments(d);
324
+ const segments = parse_path_segments(path_data);
319
325
  if (segments.length < 3)
320
326
  continue; // rectangle needs at least 3 segments (4th is Z)
321
327
  // Collect all x and y coordinates from segment endpoints
@@ -403,8 +409,7 @@ function extract_matplotlib_labels(doc, labels) {
403
409
  // Clean LaTeX: "La$_2$NiO$_4$ + NiO" -> "La2NiO4 + NiO"
404
410
  const text = clean_latex(comment.trim());
405
411
  // Get position from transform="translate(x, y)"
406
- const pos = parse_translate(group.querySelector(`g[transform]`)) ??
407
- parse_translate(group);
412
+ const pos = parse_translate(group.querySelector(`g[transform]`)) ?? parse_translate(group);
408
413
  if (!pos)
409
414
  continue;
410
415
  labels.push({ text, px_x: pos[0], px_y: pos[1] });
@@ -414,7 +419,7 @@ function extract_matplotlib_labels(doc, labels) {
414
419
  function extract_simple_labels(doc, labels) {
415
420
  for (const text_el of Array.from(doc.querySelectorAll(`.label-main`))) {
416
421
  // Get plain text content (strips tspan tags)
417
- const text = (text_el.textContent ?? ``).replace(/\s+/g, ` `).trim();
422
+ const text = (text_el.textContent ?? ``).replaceAll(/\s+/g, ` `).trim();
418
423
  if (!text.includes(`+`))
419
424
  continue; // skip non-phase labels
420
425
  // Get position from transform or x/y attributes
@@ -430,7 +435,7 @@ function extract_simple_labels(doc, labels) {
430
435
  // Infer binary components from region labels
431
436
  function infer_components(labels) {
432
437
  // Sort labels by x position, split each into phases
433
- const sorted = [...labels].sort((a, b) => a.px_x - b.px_x);
438
+ const sorted = labels.toSorted((a, b) => a.px_x - b.px_x);
434
439
  if (sorted.length < 2)
435
440
  return [`A`, `B`];
436
441
  const split = (label) => label.text.split(/\s*\+\s*/);
@@ -440,14 +445,14 @@ function infer_components(labels) {
440
445
  // For "La2NiO4 + La2O3", La2O3 is the pure A endpoint
441
446
  let comp_a = `A`;
442
447
  if (leftmost.length === 2) {
443
- const right_phases = sorted.slice(-3).flatMap(split);
444
- comp_a = leftmost.find((p) => !right_phases.includes(p)) ?? leftmost[1] ?? `A`;
448
+ const right_phases = new Set(sorted.slice(-3).flatMap(split));
449
+ comp_a = leftmost.find((p) => !right_phases.has(p)) ?? leftmost[1] ?? `A`;
445
450
  }
446
451
  // Component B: the unique phase in the rightmost region that doesn't appear on the left
447
452
  let comp_b = `B`;
448
453
  if (rightmost.length === 2) {
449
- const left_phases = sorted.slice(0, 3).flatMap(split);
450
- comp_b = rightmost.find((p) => !left_phases.includes(p)) ?? rightmost[1] ?? `B`;
454
+ const left_phases = new Set(sorted.slice(0, 3).flatMap(split));
455
+ comp_b = rightmost.find((p) => !left_phases.has(p)) ?? rightmost[1] ?? `B`;
451
456
  }
452
457
  return [comp_a, comp_b];
453
458
  }
@@ -540,12 +545,13 @@ function infer_regions(boundaries, labels, x_scale, y_scale) {
540
545
  for (let region_id = 0; region_id < next_region_id; region_id++) {
541
546
  const name = region_labels.get(region_id) ?? `Region ${region_id + 1}`;
542
547
  // Slug can be empty for non-ASCII labels like "α + β" — fall back to region_N
543
- const slug = name.toLowerCase().replace(/[^a-z0-9]+/g, `_`).replace(/^_|_$/g, ``) || `region_${region_id + 1}`;
548
+ const slug = name
549
+ .toLowerCase()
550
+ .replaceAll(/[^a-z0-9]+/g, `_`)
551
+ .replaceAll(/^_|_$/g, ``) || `region_${region_id + 1}`;
544
552
  // Find bounding box of all cells in this region
545
- let min_x = Infinity;
546
- let max_x = -Infinity;
547
- let min_y = Infinity;
548
- let max_y = -Infinity;
553
+ let [min_x, max_x] = [Infinity, -Infinity];
554
+ let [min_y, max_y] = [Infinity, -Infinity];
549
555
  for (let col = 0; col < n_cols; col++) {
550
556
  for (let row = 0; row < n_rows; row++) {
551
557
  if (cell_ids[col][row] !== region_id)
@@ -627,9 +633,7 @@ export function parse_phase_diagram_svg(svg_string) {
627
633
  const { x_scale, y_scale } = extract_axis_scales(doc, format);
628
634
  const boundaries = extract_boundaries(doc, format, x_scale, y_scale);
629
635
  const labels = extract_labels(doc, format);
630
- const components = format === `mpds`
631
- ? infer_mpds_components(doc)
632
- : infer_components(labels);
636
+ const components = format === `mpds` ? infer_mpds_components(doc) : infer_components(labels);
633
637
  if (boundaries.length === 0) {
634
638
  throw new Error(`No phase boundaries found in SVG`);
635
639
  }
@@ -666,7 +670,7 @@ function infer_mpds_components(doc) {
666
670
  // === Utility Functions ===
667
671
  // Parse stroke-width from style attribute or direct attribute (returns 0 if not found)
668
672
  function parse_stroke_width(el) {
669
- const style_match = (el.getAttribute(`style`) ?? ``).match(/stroke-width:\s*([\d.]+)/);
673
+ const style_match = /stroke-width:\s*([\d.]+)/.exec(el.getAttribute(`style`) ?? ``);
670
674
  if (style_match)
671
675
  return parseFloat(style_match[1]);
672
676
  const attr = el.getAttribute(`stroke-width`);
@@ -718,24 +722,24 @@ function find_comment_text(group) {
718
722
  // Clean LaTeX subscript notation: "La$_2$NiO$_4$" -> "La2NiO4"
719
723
  function clean_latex(text) {
720
724
  return text
721
- .replace(/\$_\{([^}]*)\}\$/g, `$1`) // $_{10}$ -> 10
722
- .replace(/\$_(\d)\$/g, `$1`) // $_2$ -> 2
723
- .replace(/\$/g, ``) // remove any remaining $
724
- .replace(/\s+/g, ` `)
725
+ .replaceAll(/\$_\{([^}]*)\}\$/g, `$1`) // $_{10}$ -> 10
726
+ .replaceAll(/\$_(\d)\$/g, `$1`) // $_2$ -> 2
727
+ .replaceAll(`$`, ``) // remove any remaining $
728
+ .replaceAll(/\s+/g, ` `)
725
729
  .trim();
726
730
  }
727
731
  // Parse SVG path data into absolute line segments [x1,y1,x2,y2]
728
732
  // Handles all SVG path commands (M/L/H/V/C/S/Q/T/A/Z, both absolute and relative)
729
733
  // Curves (C/S/Q/T/A) are approximated as straight lines from start to endpoint
730
734
  // After M/m, implicit coordinates are treated as L/l per SVG spec
731
- function parse_path_segments(d) {
735
+ function parse_path_segments(path_str) {
732
736
  const segments = [];
733
737
  let [cursor_x, cursor_y] = [0, 0];
734
738
  let [start_x, start_y] = [0, 0];
735
739
  let last_cmd = ``;
736
740
  // Numbers to skip before the endpoint x,y for each curve command
737
741
  const curve_skip = { C: 4, S: 2, Q: 2, T: 0, A: 5 };
738
- const tokens = d.match(/[MmLlHhVvCcSsQqTtAaZz]|[-+]?[\d]*\.?[\d]+(?:[eE][-+]?\d+)?/g);
742
+ const tokens = path_str.match(/[MmLlHhVvCcSsQqTtAaZz]|[-+]?[\d]*\.?[\d]+(?:[eE][-+]?\d+)?/g);
739
743
  if (!tokens)
740
744
  return segments;
741
745
  let idx = 0;
@@ -813,8 +817,8 @@ function parse_path_segments(d) {
813
817
  }
814
818
  // Parse a simple 2-point line path (M...L only). Returns null for multi-segment paths
815
819
  // to enforce the single-line contract expected by boundary extraction.
816
- function parse_ml_path(d) {
817
- const segments = parse_path_segments(d);
820
+ function parse_ml_path(path_str) {
821
+ const segments = parse_path_segments(path_str);
818
822
  if (segments.length !== 1)
819
823
  return null;
820
824
  const [x1, y1, x2, y2] = segments[0];
@@ -823,7 +827,7 @@ function parse_ml_path(d) {
823
827
  // Parse translate(x, y) or translate(x) from a transform attribute
824
828
  // Single-arg translate uses implicit y=0 per SVG spec
825
829
  function parse_translate(el) {
826
- const match = (el?.getAttribute(`transform`) ?? ``).match(/translate\(\s*([\d.eE+-]+)(?:\s*[,\s]\s*([\d.eE+-]+))?\s*\)/);
830
+ const match = /translate\(\s*([\d.eE+-]+)(?:\s*[,\s]\s*([\d.eE+-]+))?\s*\)/.exec(el?.getAttribute(`transform`) ?? ``);
827
831
  if (!match)
828
832
  return null;
829
833
  return [parseFloat(match[1]), match[2] ? parseFloat(match[2]) : 0];
@@ -837,7 +841,7 @@ function get_group_translate(el, axis) {
837
841
  function collect_unique_sorted(values) {
838
842
  if (values.length === 0)
839
843
  return [];
840
- const sorted = [...values].sort((a, b) => a - b);
844
+ const sorted = values.toSorted((a, b) => a - b);
841
845
  const unique = [sorted[0]];
842
846
  for (let idx = 1; idx < sorted.length; idx++) {
843
847
  if (Math.abs(sorted[idx] - unique[unique.length - 1]) > 1e-4) {
@@ -115,3 +115,4 @@ export declare function format_formula_svg(formula: string, use_subscripts?: boo
115
115
  export declare const format_label_svg: (label: string, use_subscripts?: boolean) => string;
116
116
  export declare const format_label_html: (label: string, use_subscripts?: boolean) => string;
117
117
  export declare function format_formula_html(formula: string, use_subscripts?: boolean): string;
118
+ export declare function compute_x_domain(x_range: [number | null, number | null] | undefined, data: PhaseDiagramData | null): Vec2;
@@ -7,17 +7,9 @@ export function convert_temp(value, from, to) {
7
7
  if (from === to)
8
8
  return value;
9
9
  // Convert to Kelvin first
10
- const kelvin = from === `°C`
11
- ? value + 273.15
12
- : from === `°F`
13
- ? (value - 32) * (5 / 9) + 273.15
14
- : value;
10
+ const kelvin = from === `°C` ? value + 273.15 : from === `°F` ? (value - 32) * (5 / 9) + 273.15 : value;
15
11
  // Convert from Kelvin to target
16
- return to === `K`
17
- ? kelvin
18
- : to === `°C`
19
- ? kelvin - 273.15
20
- : (kelvin - 273.15) * (9 / 5) + 32;
12
+ return to === `K` ? kelvin : to === `°C` ? kelvin - 273.15 : (kelvin - 273.15) * (9 / 5) + 32;
21
13
  }
22
14
  // Centralized defaults for phase diagram configuration (single source of truth)
23
15
  export const PHASE_DIAGRAM_DEFAULTS = Object.freeze({
@@ -58,8 +50,7 @@ export function merge_phase_diagram_config(config) {
58
50
  return {
59
51
  margin: { ...PHASE_DIAGRAM_DEFAULTS.margin, ...config.margin },
60
52
  font_size: config.font_size ?? PHASE_DIAGRAM_DEFAULTS.font_size,
61
- special_point_radius: config.special_point_radius ??
62
- PHASE_DIAGRAM_DEFAULTS.special_point_radius,
53
+ special_point_radius: config.special_point_radius ?? PHASE_DIAGRAM_DEFAULTS.special_point_radius,
63
54
  tie_line: { ...PHASE_DIAGRAM_DEFAULTS.tie_line, ...config.tie_line },
64
55
  colors: { ...PHASE_DIAGRAM_DEFAULTS.colors, ...config.colors },
65
56
  };
@@ -136,7 +127,10 @@ export function get_phase_color(name, format = `rgba`) {
136
127
  export function get_multi_phase_gradient(name) {
137
128
  if (!name.includes(`+`))
138
129
  return null;
139
- const phases = name.split(`+`).map((s) => s.trim()).filter(Boolean);
130
+ const phases = name
131
+ .split(`+`)
132
+ .map((s) => s.trim())
133
+ .filter(Boolean);
140
134
  if (phases.length < 2)
141
135
  return null;
142
136
  // Create evenly spaced gradient stops (phases.length >= 2 guaranteed by early return)
@@ -157,11 +151,13 @@ export function find_phase_at_point(composition, temperature, data) {
157
151
  return null;
158
152
  }
159
153
  // SVG path generator using d3-shape
160
- const path_line = line().x((d) => d[0]).y((d) => d[1]);
154
+ const path_line = line()
155
+ .x((d) => d[0])
156
+ .y((d) => d[1]);
161
157
  // Generate closed SVG path for polygon regions (min 3 points)
162
158
  export const generate_region_path = (vertices) => vertices.length < 3 ? `` : `${path_line(vertices)} Z`;
163
159
  // Generate open SVG path for boundary curves (min 2 points)
164
- export const generate_boundary_path = (points) => points.length < 2 ? `` : path_line(points) ?? ``;
160
+ export const generate_boundary_path = (points) => points.length < 2 ? `` : (path_line(points) ?? ``);
165
161
  // Compute label properties (rotation, wrapping, scale) to fit within region bounds
166
162
  export function compute_label_properties(label, bounds, font_size) {
167
163
  if (bounds.width <= 0 || bounds.height <= 0 || !label || font_size <= 0) {
@@ -245,7 +241,10 @@ export function format_temperature(value, unit = `K`) {
245
241
  function parse_two_phases(name) {
246
242
  if (!name.includes(`+`))
247
243
  return null;
248
- const parts = name.trim().split(/\s*\+\s*/).filter(Boolean);
244
+ const parts = name
245
+ .trim()
246
+ .split(/\s*\+\s*/)
247
+ .filter(Boolean);
249
248
  return parts.length === 2 ? [parts[0], parts[1]] : null;
250
249
  }
251
250
  // Find polygon edge intersections along a scan line (horizontal or vertical)
@@ -259,11 +258,10 @@ function find_polygon_intersections(vertices, fixed_val, axis) {
259
258
  const v2 = vertices[(idx + 1) % vertices.length];
260
259
  if ((v1[axis] <= fixed_val && v2[axis] > fixed_val) ||
261
260
  (v2[axis] <= fixed_val && v1[axis] > fixed_val)) {
262
- intersections.push(v1[other] +
263
- ((fixed_val - v1[axis]) / (v2[axis] - v1[axis])) * (v2[other] - v1[other]));
261
+ intersections.push(v1[other] + ((fixed_val - v1[axis]) / (v2[axis] - v1[axis])) * (v2[other] - v1[other]));
264
262
  }
265
263
  }
266
- return intersections.sort((a, b) => a - b);
264
+ return intersections.toSorted((a, b) => a - b);
267
265
  }
268
266
  function pick_bracketing_intersection_pair(intersections, position) {
269
267
  if (intersections.length < 2)
@@ -404,7 +402,7 @@ export function summarize_models(phases) {
404
402
  counts.set(phase.sublattice_count, (counts.get(phase.sublattice_count) ?? 0) + 1);
405
403
  }
406
404
  return [...counts.entries()]
407
- .sort(([sl_a], [sl_b]) => sl_a - sl_b)
405
+ .toSorted(([sl_a], [sl_b]) => sl_a - sl_b)
408
406
  .map(([sublattices, count]) => `${count}×${sublattices}-SL`)
409
407
  .join(`, `);
410
408
  }
@@ -486,7 +484,7 @@ export function tokenize_formula(formula) {
486
484
  // Any other character (lowercase, hyphen, etc.) - collect as text
487
485
  let text = char;
488
486
  idx++;
489
- while (idx < formula.length && !/[A-Z\d\-]/.test(formula[idx])) {
487
+ while (idx < formula.length && !/[A-Z\d-]/.test(formula[idx])) {
490
488
  text += formula[idx];
491
489
  idx++;
492
490
  }
@@ -530,11 +528,14 @@ export function format_formula_svg(formula, use_subscripts = true) {
530
528
  function format_label_parts(label, use_subscripts, formatter) {
531
529
  if (!use_subscripts)
532
530
  return label;
533
- return label.split(/(\s*\+\s*)/).map((part) => {
531
+ return label
532
+ .split(/(\s*\+\s*)/)
533
+ .map((part) => {
534
534
  if (part.trim() === `+`)
535
535
  return part;
536
536
  return formatter(part.trim(), use_subscripts);
537
- }).join(``);
537
+ })
538
+ .join(``);
538
539
  }
539
540
  // Format a phase region label (e.g. "La2NiO4 + NiO") as SVG with subscripts
540
541
  export const format_label_svg = (label, use_subscripts = true) => format_label_parts(label, use_subscripts, format_formula_svg);
@@ -545,7 +546,61 @@ export function format_formula_html(formula, use_subscripts = true) {
545
546
  if (!use_subscripts || !is_compound(formula))
546
547
  return formula;
547
548
  return tokenize_formula(formula)
548
- .map((token) => token.text ??
549
- (token.sub ? `<sub>${token.sub}</sub>` : `<sup>${token.sup}</sup>`))
549
+ .map((token) => token.text ?? (token.sub ? `<sub>${token.sub}</sub>` : `<sup>${token.sup}</sup>`))
550
550
  .join(``);
551
551
  }
552
+ // Compute the x-axis domain for a binary phase diagram.
553
+ // Uses explicit range if fully specified, otherwise derives from data extent
554
+ // and auto-extends to 0/1 when edge regions contain pure components.
555
+ export function compute_x_domain(x_range, data) {
556
+ const lo = x_range?.[0];
557
+ const hi = x_range?.[1];
558
+ if (lo != null && hi != null)
559
+ return [lo, hi];
560
+ if (data) {
561
+ let data_min = Infinity;
562
+ let data_max = -Infinity;
563
+ const update = (val) => {
564
+ if (val < data_min)
565
+ data_min = val;
566
+ if (val > data_max)
567
+ data_max = val;
568
+ };
569
+ for (const region of data.regions) {
570
+ for (const vertex of region.vertices)
571
+ update(vertex[0]);
572
+ }
573
+ for (const boundary of data.boundaries) {
574
+ for (const point of boundary.points)
575
+ update(point[0]);
576
+ }
577
+ for (const special_point of data.special_points ?? []) {
578
+ update(special_point.position[0]);
579
+ }
580
+ if (data_min <= data_max) {
581
+ let x_min = lo ?? data_min;
582
+ let x_max = hi ?? data_max;
583
+ // Auto-extend to 0/1 when edge regions contain a pure component AND the
584
+ // data already nearly reaches the boundary
585
+ const comp_at_edge = (comp, x_val) => {
586
+ const re = new RegExp(`\\b${comp.replaceAll(/[.*+?^${}()|[\]\\]/g, `\\$&`)}\\b`);
587
+ return data.regions.some((region) => re.test(region.name) &&
588
+ region.vertices.some((vertex) => Math.abs(vertex[0] - x_val) < 1e-6));
589
+ };
590
+ if (lo == null &&
591
+ x_min < 0.05 &&
592
+ data.components[0] &&
593
+ comp_at_edge(data.components[0], x_min)) {
594
+ x_min = 0;
595
+ }
596
+ if (hi == null &&
597
+ x_max > 0.95 &&
598
+ data.components[1] &&
599
+ comp_at_edge(data.components[1], x_max)) {
600
+ x_max = 1;
601
+ }
602
+ return [x_min, x_max];
603
+ }
604
+ }
605
+ return [lo ?? 0, hi ?? 1];
606
+ }
@@ -1,6 +1,31 @@
1
- <script lang="ts">import { AXIS_LABEL_CONTAINER } from './axis-utils';
2
- import InteractiveAxisLabel from './InteractiveAxisLabel.svelte';
3
- let { x, y, rotate = false, label = ``, options, selected_key, color, loading = false, axis_type, on_select, } = $props();
1
+ <script lang="ts">
2
+ import { AXIS_LABEL_CONTAINER } from './axis-utils'
3
+ import type { AxisOption } from './types'
4
+ import InteractiveAxisLabel from './InteractiveAxisLabel.svelte'
5
+
6
+ let {
7
+ x,
8
+ y,
9
+ rotate = false,
10
+ label = ``,
11
+ options,
12
+ selected_key,
13
+ color,
14
+ loading = false,
15
+ axis_type,
16
+ on_select,
17
+ }: {
18
+ x: number
19
+ y: number
20
+ rotate?: boolean
21
+ label?: string
22
+ options?: AxisOption[]
23
+ selected_key?: string
24
+ color?: string | null
25
+ loading?: boolean
26
+ axis_type: `x` | `x2` | `y` | `y2`
27
+ on_select?: (key: string) => void
28
+ } = $props()
4
29
  </script>
5
30
 
6
31
  <foreignObject