matterviz 0.3.5 → 0.3.7

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 (229) hide show
  1. package/dist/MillerIndexInput.svelte +5 -5
  2. package/dist/api/optimade.js +3 -3
  3. package/dist/brillouin/BrillouinZone.svelte +5 -2
  4. package/dist/brillouin/BrillouinZone.svelte.d.ts +1 -1
  5. package/dist/brillouin/BrillouinZoneExportPane.svelte +1 -3
  6. package/dist/brillouin/BrillouinZoneInfoPane.svelte +1 -1
  7. package/dist/brillouin/BrillouinZoneScene.svelte +5 -5
  8. package/dist/brillouin/compute.js +21 -21
  9. package/dist/brillouin/index.d.ts +1 -1
  10. package/dist/brillouin/index.js +0 -1
  11. package/dist/brillouin/types.d.ts +8 -13
  12. package/dist/chempot-diagram/ChemPotDiagram.svelte +3 -3
  13. package/dist/chempot-diagram/ChemPotDiagram2D.svelte +3 -4
  14. package/dist/chempot-diagram/ChemPotDiagram3D.svelte +33 -34
  15. package/dist/chempot-diagram/compute.js +1 -7
  16. package/dist/chempot-diagram/temperature.d.ts +1 -1
  17. package/dist/chempot-diagram/temperature.js +1 -3
  18. package/dist/chempot-diagram/types.d.ts +4 -9
  19. package/dist/colors/index.js +5 -5
  20. package/dist/composition/Composition.svelte +2 -1
  21. package/dist/composition/Formula.svelte +7 -4
  22. package/dist/composition/FormulaFilter.svelte +1 -3
  23. package/dist/composition/format.js +4 -4
  24. package/dist/composition/parse.d.ts +2 -1
  25. package/dist/composition/parse.js +61 -46
  26. package/dist/convex-hull/ConvexHull2D.svelte +62 -51
  27. package/dist/convex-hull/ConvexHull3D.svelte +101 -90
  28. package/dist/convex-hull/ConvexHull4D.svelte +70 -58
  29. package/dist/convex-hull/ConvexHullControls.svelte +24 -35
  30. package/dist/convex-hull/ConvexHullInfoPane.svelte +8 -5
  31. package/dist/convex-hull/ConvexHullInfoPane.svelte.d.ts +2 -0
  32. package/dist/convex-hull/ConvexHullStats.svelte +9 -2
  33. package/dist/convex-hull/ConvexHullStats.svelte.d.ts +2 -0
  34. package/dist/convex-hull/GasPressureControls.svelte +7 -7
  35. package/dist/convex-hull/StructurePopup.svelte +65 -30
  36. package/dist/convex-hull/StructurePopup.svelte.d.ts +6 -6
  37. package/dist/convex-hull/TemperatureSlider.svelte +8 -5
  38. package/dist/convex-hull/barycentric-coords.d.ts +2 -2
  39. package/dist/convex-hull/barycentric-coords.js +2 -2
  40. package/dist/convex-hull/gas-thermodynamics.js +2 -4
  41. package/dist/convex-hull/helpers.d.ts +13 -2
  42. package/dist/convex-hull/helpers.js +37 -16
  43. package/dist/convex-hull/index.d.ts +1 -0
  44. package/dist/convex-hull/index.js +1 -0
  45. package/dist/convex-hull/thermodynamics.d.ts +2 -1
  46. package/dist/convex-hull/thermodynamics.js +7 -7
  47. package/dist/convex-hull/types.d.ts +15 -15
  48. package/dist/effects.svelte.d.ts +12 -0
  49. package/dist/effects.svelte.js +37 -0
  50. package/dist/element/BohrAtom.svelte +4 -4
  51. package/dist/element/data.json.gz.d.ts +3 -1
  52. package/dist/element/index.d.ts +1 -1
  53. package/dist/element/index.js +0 -1
  54. package/dist/fermi-surface/FermiSurface.svelte +4 -4
  55. package/dist/fermi-surface/FermiSurface.svelte.d.ts +1 -1
  56. package/dist/fermi-surface/FermiSurfaceControls.svelte +15 -19
  57. package/dist/fermi-surface/FermiSurfaceControls.svelte.d.ts +1 -1
  58. package/dist/fermi-surface/FermiSurfaceScene.svelte +8 -6
  59. package/dist/fermi-surface/compute.js +2 -2
  60. package/dist/fermi-surface/export.js +13 -26
  61. package/dist/fermi-surface/parse.js +8 -12
  62. package/dist/fermi-surface/types.d.ts +2 -5
  63. package/dist/heatmap-matrix/HeatmapMatrix.svelte +21 -3
  64. package/dist/heatmap-matrix/index.js +6 -6
  65. package/dist/io/decompress.d.ts +2 -1
  66. package/dist/io/decompress.js +1 -1
  67. package/dist/io/export.js +1 -1
  68. package/dist/io/index.d.ts +1 -1
  69. package/dist/io/index.js +0 -1
  70. package/dist/io/url-drop.js +7 -1
  71. package/dist/isosurface/IsosurfaceControls.svelte +11 -25
  72. package/dist/isosurface/slice.js +1 -1
  73. package/dist/isosurface/types.js +12 -12
  74. package/dist/labels.d.ts +1 -1
  75. package/dist/labels.js +14 -11
  76. package/dist/layout/InfoTag.svelte +6 -4
  77. package/dist/layout/PropertyFilter.svelte +4 -2
  78. package/dist/layout/json-tree/JsonTree.svelte +22 -14
  79. package/dist/layout/json-tree/JsonValue.svelte +2 -2
  80. package/dist/layout/json-tree/types.d.ts +3 -2
  81. package/dist/layout/json-tree/types.js +0 -1
  82. package/dist/layout/json-tree/utils.d.ts +4 -4
  83. package/dist/layout/json-tree/utils.js +12 -20
  84. package/dist/marching-cubes.js +13 -15
  85. package/dist/math.d.ts +11 -1
  86. package/dist/math.js +15 -6
  87. package/dist/overlays/DragControlTab.svelte +98 -0
  88. package/dist/overlays/DragControlTab.svelte.d.ts +8 -0
  89. package/dist/overlays/DraggablePane.svelte +7 -84
  90. package/dist/overlays/index.d.ts +1 -0
  91. package/dist/overlays/index.js +1 -0
  92. package/dist/periodic-table/PeriodicTable.svelte +11 -11
  93. package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte +4 -2
  94. package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte.d.ts +1 -1
  95. package/dist/phase-diagram/PhaseDiagramControls.svelte +4 -9
  96. package/dist/phase-diagram/PhaseDiagramControls.svelte.d.ts +1 -1
  97. package/dist/phase-diagram/PhaseDiagramExportPane.svelte +2 -10
  98. package/dist/phase-diagram/PhaseDiagramTooltip.svelte +2 -3
  99. package/dist/phase-diagram/TdbInfoPanel.svelte +3 -3
  100. package/dist/phase-diagram/build-diagram.js +11 -18
  101. package/dist/phase-diagram/diagram-input.d.ts +5 -9
  102. package/dist/phase-diagram/index.d.ts +2 -2
  103. package/dist/phase-diagram/index.js +0 -2
  104. package/dist/phase-diagram/parse.d.ts +2 -2
  105. package/dist/phase-diagram/parse.js +6 -10
  106. package/dist/phase-diagram/svg-to-diagram.js +15 -15
  107. package/dist/phase-diagram/types.d.ts +5 -11
  108. package/dist/phase-diagram/utils.d.ts +2 -2
  109. package/dist/phase-diagram/utils.js +9 -11
  110. package/dist/plot/BarPlot.svelte +162 -314
  111. package/dist/plot/BarPlot.svelte.d.ts +5 -4
  112. package/dist/plot/BarPlotControls.svelte.d.ts +1 -1
  113. package/dist/plot/BinnedScatterPlot.svelte +1114 -0
  114. package/dist/plot/BinnedScatterPlot.svelte.d.ts +66 -0
  115. package/dist/plot/ColorBar.svelte +19 -17
  116. package/dist/plot/ColorBar.svelte.d.ts +1 -1
  117. package/dist/plot/FillArea.svelte +2 -4
  118. package/dist/plot/FillArea.svelte.d.ts +1 -1
  119. package/dist/plot/Histogram.svelte +167 -281
  120. package/dist/plot/Histogram.svelte.d.ts +1 -1
  121. package/dist/plot/HistogramControls.svelte.d.ts +1 -1
  122. package/dist/plot/InteractiveAxisLabel.svelte +5 -3
  123. package/dist/plot/InteractiveAxisLabel.svelte.d.ts +1 -1
  124. package/dist/plot/PlotAxis.svelte +169 -0
  125. package/dist/plot/PlotAxis.svelte.d.ts +24 -0
  126. package/dist/plot/PlotControls.svelte.d.ts +1 -1
  127. package/dist/plot/ReferenceLine3D.svelte +53 -51
  128. package/dist/plot/ReferencePlane.svelte +39 -42
  129. package/dist/plot/ScatterPlot.svelte +300 -367
  130. package/dist/plot/ScatterPlot.svelte.d.ts +8 -5
  131. package/dist/plot/ScatterPlot3D.svelte +33 -6
  132. package/dist/plot/ScatterPlot3D.svelte.d.ts +3 -2
  133. package/dist/plot/ScatterPlot3DControls.svelte +9 -9
  134. package/dist/plot/ScatterPlotControls.svelte +3 -4
  135. package/dist/plot/ScatterPoint.svelte +18 -27
  136. package/dist/plot/ScatterPoint.svelte.d.ts +4 -3
  137. package/dist/plot/Surface3D.svelte +4 -7
  138. package/dist/plot/ZeroLines.svelte +2 -1
  139. package/dist/plot/ZeroLines.svelte.d.ts +2 -1
  140. package/dist/plot/ZoomRect.svelte +2 -2
  141. package/dist/plot/ZoomRect.svelte.d.ts +3 -3
  142. package/dist/plot/adaptive-density.d.ts +69 -0
  143. package/dist/plot/adaptive-density.js +191 -0
  144. package/dist/plot/auto-place.d.ts +43 -0
  145. package/dist/plot/auto-place.js +122 -0
  146. package/dist/plot/axis-utils.js +3 -5
  147. package/dist/plot/binned-scatter-types.d.ts +59 -0
  148. package/dist/plot/binned-scatter-types.js +1 -0
  149. package/dist/plot/data-cleaning.js +1 -1
  150. package/dist/plot/data-transform.js +1 -1
  151. package/dist/plot/fill-utils.d.ts +4 -9
  152. package/dist/plot/fill-utils.js +29 -44
  153. package/dist/plot/index.d.ts +4 -0
  154. package/dist/plot/index.js +2 -0
  155. package/dist/plot/interactions.d.ts +4 -4
  156. package/dist/plot/interactions.js +4 -3
  157. package/dist/plot/layout.d.ts +20 -2
  158. package/dist/plot/layout.js +59 -16
  159. package/dist/plot/reference-line.d.ts +1 -1
  160. package/dist/plot/reference-line.js +9 -11
  161. package/dist/plot/scales.d.ts +1 -1
  162. package/dist/plot/scales.js +20 -23
  163. package/dist/plot/types.d.ts +30 -58
  164. package/dist/plot/types.js +2 -6
  165. package/dist/plot/utils/label-placement.d.ts +24 -3
  166. package/dist/plot/utils/label-placement.js +82 -12
  167. package/dist/plot/utils/series-visibility.d.ts +8 -2
  168. package/dist/plot/utils/series-visibility.js +23 -5
  169. package/dist/rdf/RdfPlot.svelte +5 -5
  170. package/dist/rdf/calc-rdf.js +3 -3
  171. package/dist/sanitize.d.ts +2 -0
  172. package/dist/sanitize.js +2 -0
  173. package/dist/spectral/Bands.svelte +1 -1
  174. package/dist/spectral/BandsAndDos.svelte +22 -16
  175. package/dist/spectral/BrillouinBandsDos.svelte +20 -16
  176. package/dist/spectral/Dos.svelte +1 -1
  177. package/dist/spectral/helpers.d.ts +4 -2
  178. package/dist/spectral/helpers.js +44 -35
  179. package/dist/spectral/index.d.ts +1 -1
  180. package/dist/spectral/index.js +0 -1
  181. package/dist/structure/AtomLegend.svelte +23 -6
  182. package/dist/structure/AtomLegend.svelte.d.ts +1 -0
  183. package/dist/structure/CanvasTooltip.svelte +9 -9
  184. package/dist/structure/CanvasTooltip.svelte.d.ts +1 -1
  185. package/dist/structure/CellSelect.svelte +14 -16
  186. package/dist/structure/Structure.svelte +317 -68
  187. package/dist/structure/Structure.svelte.d.ts +4 -2
  188. package/dist/structure/StructureControls.svelte +20 -45
  189. package/dist/structure/StructureExportPane.svelte +2 -1
  190. package/dist/structure/StructureInfoPane.svelte +10 -8
  191. package/dist/structure/StructureScene.svelte +527 -177
  192. package/dist/structure/StructureScene.svelte.d.ts +5 -2
  193. package/dist/structure/atom-properties.js +4 -4
  194. package/dist/structure/bond-order-perception.js +115 -98
  195. package/dist/structure/bonding.d.ts +27 -1
  196. package/dist/structure/bonding.js +187 -16
  197. package/dist/structure/export.js +1 -1
  198. package/dist/structure/index.d.ts +3 -2
  199. package/dist/structure/index.js +0 -2
  200. package/dist/structure/parse.js +88 -59
  201. package/dist/symmetry/WyckoffTable.svelte +7 -0
  202. package/dist/symmetry/index.js +13 -14
  203. package/dist/table/HeatmapTable.svelte +45 -66
  204. package/dist/table/HeatmapTable.svelte.d.ts +1 -1
  205. package/dist/table/ToggleMenu.svelte +19 -10
  206. package/dist/theme/themes.mjs +12 -0
  207. package/dist/tooltip/index.d.ts +1 -1
  208. package/dist/tooltip/index.js +0 -1
  209. package/dist/trajectory/Trajectory.svelte +43 -15
  210. package/dist/trajectory/TrajectoryInfoPane.svelte +2 -2
  211. package/dist/trajectory/extract.js +1 -1
  212. package/dist/trajectory/frame-reader.js +4 -4
  213. package/dist/trajectory/helpers.d.ts +5 -4
  214. package/dist/trajectory/helpers.js +9 -17
  215. package/dist/trajectory/index.d.ts +2 -2
  216. package/dist/trajectory/index.js +2 -2
  217. package/dist/trajectory/parse/ase.js +4 -4
  218. package/dist/trajectory/parse/hdf5.js +1 -1
  219. package/dist/trajectory/parse/index.js +2 -3
  220. package/dist/trajectory/parse/lammps.js +1 -1
  221. package/dist/trajectory/parse/vasp.js +1 -1
  222. package/dist/trajectory/plotting.d.ts +1 -1
  223. package/dist/trajectory/plotting.js +38 -38
  224. package/dist/trajectory/types.d.ts +1 -1
  225. package/dist/utils.d.ts +1 -0
  226. package/dist/utils.js +9 -0
  227. package/dist/xrd/calc-xrd.js +3 -4
  228. package/dist/xrd/parse.js +1 -1
  229. package/package.json +42 -22
@@ -2,13 +2,9 @@ import * as constants from './constants';
2
2
  import { compute_vertex_normals } from './marching-cubes';
3
3
  const parse_number_tokens = (line) => line.split(/\s+/).filter(Boolean);
4
4
  // Parse whitespace-separated floats from a line (optimized with unary +)
5
- function parse_floats(line) {
6
- return parse_number_tokens(line).map((part) => +part);
7
- }
5
+ const parse_floats = (line) => parse_number_tokens(line).map((part) => +part);
8
6
  // Parse whitespace-separated integers from a line
9
- function parse_ints(line) {
10
- return parse_number_tokens(line).map((part) => parseInt(part, 10));
11
- }
7
+ const parse_ints = (line) => parse_number_tokens(line).map((part) => parseInt(part, 10));
12
8
  // Parse BXSF (Band-XSF) format used by XCrySDen, Quantum ESPRESSO, etc.
13
9
  // Format specification: http://www.xcrysden.org/doc/XSF.html
14
10
  function parse_bxsf(content) {
@@ -304,12 +300,12 @@ function parse_fermi_json(content) {
304
300
  return data.fermi_surface;
305
301
  }
306
302
  if (data.band_structure?.energies || data.bands?.energies) {
307
- const bs = data.band_structure || data.bands;
303
+ const bs = data.band_structure ?? data.bands;
308
304
  return {
309
305
  energies: bs.energies,
310
- k_grid: bs.k_grid || bs.kgrid,
311
- k_lattice: bs.k_lattice || bs.reciprocal_lattice,
312
- fermi_energy: bs.fermi_energy || bs.efermi || 0,
306
+ k_grid: bs.k_grid ?? bs.kgrid,
307
+ k_lattice: bs.k_lattice ?? bs.reciprocal_lattice,
308
+ fermi_energy: bs.fermi_energy ?? bs.efermi ?? 0,
313
309
  n_bands: bs.n_bands || bs.nbands || bs.energies[0]?.length || 0,
314
310
  n_spins: bs.n_spins || bs.nspins || bs.energies.length || 1,
315
311
  };
@@ -412,7 +408,7 @@ function parse_ifermi_surface(data) {
412
408
  }
413
409
  }
414
410
  // Compute total surface area
415
- const total_area = isosurfaces.reduce((sum, iso) => sum + (iso.area || 0), 0);
411
+ const total_area = isosurfaces.reduce((sum, iso) => sum + (iso.area ?? 0), 0);
416
412
  return {
417
413
  isosurfaces,
418
414
  k_lattice,
@@ -482,7 +478,7 @@ export function parse_fermi_file(content, filename) {
482
478
  // FRMSF format detection (starts with grid dimensions)
483
479
  const first_line = trimmed.split(/\r?\n/)[0];
484
480
  const first_tokens = first_line.split(/\s+/).filter(Boolean);
485
- if (first_tokens.length === 3 && first_tokens.every((t) => /^\d+$/.test(t))) {
481
+ if (first_tokens.length === 3 && first_tokens.every((token) => /^\d+$/.test(token))) {
486
482
  try {
487
483
  return parse_frmsf(content);
488
484
  }
@@ -1,4 +1,4 @@
1
- import type { Matrix3x3, Vec3 } from '../math';
1
+ import type { Matrix3x3, Point2D, Vec3 } from '../math';
2
2
  import type { TooltipConfig, TooltipProp } from '../tooltip';
3
3
  export type SpinChannel = `up` | `down` | null;
4
4
  export type RepresentationMode = `solid` | `wireframe` | `transparent`;
@@ -95,10 +95,7 @@ export interface FermiHoverData {
95
95
  spin: SpinChannel;
96
96
  position_cartesian: Vec3;
97
97
  position_fractional: Vec3 | null;
98
- screen_position: {
99
- x: number;
100
- y: number;
101
- };
98
+ screen_position: Point2D;
102
99
  surface_color?: string;
103
100
  property_value?: number;
104
101
  property_name?: string;
@@ -4,6 +4,7 @@
4
4
  import { format_num } from '../labels'
5
5
  import type { AxisConfig } from '../plot'
6
6
  import ColorBar from '../plot/ColorBar.svelte'
7
+ import { make_change_detector } from '../utils'
7
8
  import * as d3_sc from 'd3-scale-chromatic'
8
9
  import { type ComponentProps, onDestroy, onMount, type Snippet } from 'svelte'
9
10
  import type { HTMLAttributes } from 'svelte/elements'
@@ -212,6 +213,7 @@
212
213
  // === Value resolution ===
213
214
  let x_keys = $derived(x_items.map((item) => item.key ?? item.label))
214
215
  let y_keys = $derived(y_items.map((item) => item.key ?? item.label))
216
+ let interaction_axis_signature = $derived(JSON.stringify([x_keys, y_keys]))
215
217
  let highlight_x_key_set = $derived(new SvelteSet(highlight_x_keys))
216
218
  let highlight_y_key_set = $derived(new SvelteSet(highlight_y_keys))
217
219
  let search_query_norm = $derived(search_query.trim().toLowerCase())
@@ -577,9 +579,8 @@
577
579
  return vis_y.slice(start_pos, end_pos)
578
580
  })
579
581
 
580
- function is_selected_cell(x_idx: number, y_idx: number): boolean {
581
- return selected_cell_key_set.has(cell_pos_key(x_idx, y_idx))
582
- }
582
+ const is_selected_cell = (x_idx: number, y_idx: number): boolean =>
583
+ selected_cell_key_set.has(cell_pos_key(x_idx, y_idx))
583
584
 
584
585
  let vis_x_pos_map = $derived.by(() => {
585
586
  const position_map = new SvelteMap<number, number>()
@@ -1086,6 +1087,23 @@
1086
1087
 
1087
1088
  // Tooltip state: only used for custom tooltip snippets (function tooltips)
1088
1089
  let tooltip_cell: CellContext | null = $state(null)
1090
+ const axis_changed = make_change_detector()
1091
+ $effect(() => {
1092
+ if (!axis_changed(interaction_axis_signature)) return
1093
+ cancel_raf(active_cell_raf)
1094
+ // Cancel delayed clicks before old cell coordinates can fire on new axes.
1095
+ clear_pending_click()
1096
+ active_cell = null
1097
+ pinned_cell = null
1098
+ selected_cells = []
1099
+ last_selected_cell = null
1100
+ brush_start = null
1101
+ brush_end = null
1102
+ last_hover_x = -1
1103
+ last_hover_y = -1
1104
+ tooltip_cell = null
1105
+ tooltip_div?.classList.remove(`visible`)
1106
+ })
1089
1107
 
1090
1108
  onMount(() => {
1091
1109
  update_viewport_state()
@@ -33,15 +33,15 @@ export function matrix_to_rows(x_items, y_items, values) {
33
33
  return row;
34
34
  });
35
35
  }
36
+ function escape_csv_field(value) {
37
+ const field = String(value ?? ``);
38
+ if (!/[",\n\r]/.test(field))
39
+ return field;
40
+ return `"${field.replaceAll(`"`, `""`)}"`;
41
+ }
36
42
  export function rows_to_csv(rows) {
37
43
  if (!rows.length)
38
44
  return ``;
39
- const escape_csv_field = (value) => {
40
- const field = String(value ?? ``);
41
- if (!/[",\n\r]/.test(field))
42
- return field;
43
- return `"${field.replaceAll(`"`, `""`)}"`;
44
- };
45
45
  const headers = Object.keys(rows[0]);
46
46
  const lines = [
47
47
  headers.map((header) => escape_csv_field(header)).join(`,`),
@@ -1,4 +1,5 @@
1
- import { COMPRESSION_EXTENSIONS, COMPRESSION_FORMATS } from '../constants';
1
+ import type { COMPRESSION_EXTENSIONS } from '../constants';
2
+ import { COMPRESSION_FORMATS } from '../constants';
2
3
  export type CompressionFormat = keyof typeof COMPRESSION_FORMATS;
3
4
  export type CompressionExtension = (typeof COMPRESSION_EXTENSIONS)[number];
4
5
  export declare function detect_compression_format(filename: string): CompressionFormat | null;
@@ -1,4 +1,4 @@
1
- import { COMPRESSION_EXTENSIONS, COMPRESSION_EXTENSIONS_REGEX, COMPRESSION_FORMATS, } from '../constants';
1
+ import { COMPRESSION_EXTENSIONS_REGEX, COMPRESSION_FORMATS } from '../constants';
2
2
  export function detect_compression_format(filename) {
3
3
  const lower = filename.toLowerCase();
4
4
  for (const [format, extensions] of Object.entries(COMPRESSION_FORMATS)) {
package/dist/io/export.js CHANGED
@@ -91,7 +91,7 @@ export function export_canvas_as_png(canvas, structure_or_filename, png_dpi = 15
91
91
  // Helper to ensure font-family is set on SVG root
92
92
  function set_svg_font_family(svg) {
93
93
  const style = svg.getAttribute(`style`) || ``;
94
- if (!/font-family/.test(style)) {
94
+ if (!style.includes(`font-family`)) {
95
95
  svg.setAttribute(`style`, `${style}${style ? `;` : ``}font-family:sans-serif;`);
96
96
  }
97
97
  // Also set as attribute for extra robustness
@@ -3,5 +3,5 @@ export * from './export';
3
3
  export * from './fetch';
4
4
  export * from './file-drop';
5
5
  export * from './is-binary';
6
- export * from './types';
6
+ export type * from './types';
7
7
  export * from './url-drop';
package/dist/io/index.js CHANGED
@@ -3,5 +3,4 @@ export * from './export';
3
3
  export * from './fetch';
4
4
  export * from './file-drop';
5
5
  export * from './is-binary';
6
- export * from './types';
7
6
  export * from './url-drop';
@@ -90,6 +90,7 @@ export async function load_from_url(url, callback) {
90
90
  // Skip Range requests for known text formats to avoid production server issues
91
91
  // Include VASP files that don't have extensions (POSCAR, XDATCAR, CONTCAR)
92
92
  const is_known_text = TEXT_EXTENSIONS.has(ext) || VASP_BASENAME_RE.test(url_basename);
93
+ let binary_callback_args;
93
94
  if (!is_known_text) {
94
95
  try {
95
96
  // Check for magic bytes only for unknown formats
@@ -102,7 +103,10 @@ export async function load_from_url(url, callback) {
102
103
  const resp = await fetch(url);
103
104
  if (!resp.ok)
104
105
  throw new Error(`Fetch failed: ${resp.status}`);
105
- return callback(await resp.arrayBuffer(), extract_filename(resp.headers, url_basename));
106
+ binary_callback_args = [
107
+ await resp.arrayBuffer(),
108
+ extract_filename(resp.headers, url_basename),
109
+ ];
106
110
  }
107
111
  }
108
112
  }
@@ -110,6 +114,8 @@ export async function load_from_url(url, callback) {
110
114
  // Fall through to text fetch if HEAD request fails
111
115
  }
112
116
  }
117
+ if (binary_callback_args)
118
+ return callback(...binary_callback_args);
113
119
  const resp = await fetch(url);
114
120
  if (!resp.ok)
115
121
  throw new Error(`Fetch failed: ${resp.status}`);
@@ -68,9 +68,7 @@
68
68
  <div class="pane-row compact-row">
69
69
  {#if volumes.length > 1}
70
70
  <label
71
- {@attach tooltip({
72
- content: `Select which volume to display (e.g. charge vs magnetization)`,
73
- })}
71
+ {@attach tooltip({ content: `Select which volume to display (e.g. charge vs magnetization)` })}
74
72
  >
75
73
  <span>Volume:</span>
76
74
  <select bind:value={active_volume_idx}>
@@ -81,15 +79,13 @@
81
79
  </label>
82
80
  {/if}
83
81
  <label
84
- {@attach tooltip({
85
- content: `Number of isosurface shells at different density thresholds`,
86
- })}
82
+ {@attach tooltip({ content: `Number of isosurface shells at different density thresholds` })}
87
83
  >
88
84
  <span>Layers:</span>
89
85
  <select
90
86
  value={n_layers}
91
87
  onchange={(event) =>
92
- set_layer_count(Number((event.target as HTMLSelectElement).value))}
88
+ set_layer_count(Number(event.currentTarget.value))}
93
89
  >
94
90
  {#each [1, 2, 3, 4, 5] as count (count)}
95
91
  <option value={count}>{count}</option>
@@ -99,10 +95,7 @@
99
95
  <!-- Sync both settings.show_negative (single-layer fallback) and all layer entries
100
96
  so the toggle works consistently regardless of which mode is active -->
101
97
  <label
102
- {@attach tooltip({
103
- content:
104
- `Show negative lobe at −isovalue (for orbitals, ESP, magnetization)`,
105
- })}
98
+ {@attach tooltip({ content: `Show negative lobe at −isovalue (for orbitals, ESP, magnetization)` })}
106
99
  >
107
100
  <span>Neg. lobe</span>
108
101
  <input
@@ -111,7 +104,7 @@
111
104
  ? settings.layers?.some((layer) => layer.show_negative) ?? false
112
105
  : settings.show_negative}
113
106
  onchange={(event) => {
114
- const checked = (event.target as HTMLInputElement).checked
107
+ const checked = event.currentTarget.checked
115
108
  settings.show_negative = checked
116
109
  if (settings.layers) {
117
110
  settings.layers = settings.layers.map((layer) => ({
@@ -143,7 +136,7 @@
143
136
  type="color"
144
137
  value={layer.color}
145
138
  onchange={(event) =>
146
- update_layer(idx, { color: (event.target as HTMLInputElement).value })}
139
+ update_layer(idx, { color: event.currentTarget.value })}
147
140
  />
148
141
  <input
149
142
  type="range"
@@ -153,7 +146,7 @@
153
146
  value={layer.isovalue}
154
147
  oninput={(event) =>
155
148
  update_layer(idx, {
156
- isovalue: Number((event.target as HTMLInputElement).value),
149
+ isovalue: Number(event.currentTarget.value),
157
150
  })}
158
151
  style="flex: 1; min-width: 60px"
159
152
  />
@@ -166,7 +159,7 @@
166
159
  value={layer.opacity}
167
160
  oninput={(event) =>
168
161
  update_layer(idx, {
169
- opacity: Number((event.target as HTMLInputElement).value),
162
+ opacity: Number(event.currentTarget.value),
170
163
  })}
171
164
  style="width: 50px"
172
165
  title="Opacity: {format_num(layer.opacity, `.2f`)}"
@@ -176,9 +169,7 @@
176
169
  {:else}
177
170
  <!-- Single-layer: isovalue slider full width -->
178
171
  <label
179
- {@attach tooltip({
180
- content: `Density threshold — surface is drawn where grid values equal this`,
181
- })}
172
+ {@attach tooltip({ content: `Density threshold — surface is drawn where grid values equal this` })}
182
173
  >
183
174
  <span>Isovalue:</span>
184
175
  <input
@@ -194,9 +185,7 @@
194
185
  <!-- Opacity + colors on one row -->
195
186
  <div class="pane-row compact-row">
196
187
  <label
197
- {@attach tooltip({
198
- content: `Surface transparency — lower values reveal inner structure`,
199
- })}
188
+ {@attach tooltip({ content: `Surface transparency — lower values reveal inner structure` })}
200
189
  >
201
190
  <span>Opacity:</span>
202
191
  <input
@@ -226,10 +215,7 @@
226
215
 
227
216
  {#if volumes?.[active_volume_idx]?.periodic}
228
217
  <label
229
- {@attach tooltip({
230
- content:
231
- `Extend isosurface beyond cell boundaries to close partial spheres (fraction of cell)`,
232
- })}
218
+ {@attach tooltip({ content: `Extend isosurface beyond cell boundaries to close partial spheres (fraction of cell)` })}
233
219
  >
234
220
  Halo: {format_num(settings.halo, `.2f`)}
235
221
  <input
@@ -2,6 +2,7 @@
2
2
  // crystallographic plane defined by Miller indices, using trilinear interpolation.
3
3
  import { reciprocal_lattice } from '../brillouin';
4
4
  import * as math from '../math';
5
+ const safe_mod = (val, dim) => ((val % dim) + dim) % dim;
5
6
  // Trilinear interpolation of a scalar 3D grid at fractional coordinates.
6
7
  // Periodic grids wrap with modulo; non-periodic return 0 for out-of-bounds.
7
8
  export function trilinear_interpolate(grid, fx, fy, fz, periodic) {
@@ -19,7 +20,6 @@ export function trilinear_interpolate(grid, fx, fy, fz, periodic) {
19
20
  if (fx < 0 || fx > 1 || fy < 0 || fy > 1 || fz < 0 || fz > 1)
20
21
  return 0;
21
22
  }
22
- const safe_mod = (val, dim) => ((val % dim) + dim) % dim;
23
23
  const x0 = periodic
24
24
  ? safe_mod(Math.floor(gx), nx)
25
25
  : Math.max(0, Math.min(Math.floor(gx), nx - 2));
@@ -34,6 +34,12 @@ export function grid_data_range(grid) {
34
34
  const abs_max = Math.max(Math.abs(min_val), Math.abs(max_val));
35
35
  return { min: min_val, max: max_val, abs_max, mean: count > 0 ? sum / count : 0 };
36
36
  }
37
+ const wrap_grid_idx = (val, size) => ((val % size) + size) % size;
38
+ const clamp_dim = (src, fac) => Math.min(src, Math.max(2, Math.ceil(src / fac)));
39
+ const partition_ranges = (n_out, n_src) => Array.from({ length: n_out }, (_, idx) => [
40
+ Math.round((idx * n_src) / n_out),
41
+ Math.round(((idx + 1) * n_src) / n_out),
42
+ ]);
37
43
  // Pad a periodic 3D grid with halo cells from the opposite face so isosurfaces
38
44
  // extend beyond the unit cell and close into complete enclosed shapes.
39
45
  // Returns a larger grid with dims [nx+2*pad, ny+2*pad, nz+2*pad] and the
@@ -49,16 +55,15 @@ export function pad_periodic_grid(grid, dims, pad_fraction) {
49
55
  const out_nx = nx + 2 * px;
50
56
  const out_ny = ny + 2 * py;
51
57
  const out_nz = nz + 2 * pz;
52
- const wrap = (val, size) => ((val % size) + size) % size;
53
58
  const out = Array(out_nx);
54
59
  for (let ix = 0; ix < out_nx; ix++) {
55
60
  const plane = Array(out_ny);
56
- const src_x = wrap(ix - px, nx);
61
+ const src_x = wrap_grid_idx(ix - px, nx);
57
62
  for (let iy = 0; iy < out_ny; iy++) {
58
63
  const row = Array(out_nz);
59
- const src_y = wrap(iy - py, ny);
64
+ const src_y = wrap_grid_idx(iy - py, ny);
60
65
  for (let iz = 0; iz < out_nz; iz++) {
61
- row[iz] = grid[src_x][src_y][wrap(iz - pz, nz)];
66
+ row[iz] = grid[src_x][src_y][wrap_grid_idx(iz - pz, nz)];
62
67
  }
63
68
  plane[iy] = row;
64
69
  }
@@ -85,7 +90,6 @@ export function downsample_grid(grid, dims, max_points = MAX_GRID_POINTS) {
85
90
  // A single cbrt step can overshoot for anisotropic grids where max(2,...)
86
91
  // clamping prevents a small axis from shrinking below 2.
87
92
  // clamp_dim: returns 1 for single-cell axes, otherwise clamps to [2, src]
88
- const clamp_dim = (src, fac) => Math.min(src, Math.max(2, Math.ceil(src / fac)));
89
93
  let factor = Math.ceil(Math.cbrt(total / max_points));
90
94
  let new_nx = clamp_dim(nx, factor);
91
95
  let new_ny = clamp_dim(ny, factor);
@@ -103,13 +107,9 @@ export function downsample_grid(grid, dims, max_points = MAX_GRID_POINTS) {
103
107
  // Proportional partitioning: evenly divides [0, n) into new_n non-empty blocks.
104
108
  // Unlike fixed-stride (ix * factor), this is safe when max(2,...) clamping
105
109
  // produces more output cells than ceil(n/factor) would — no empty blocks.
106
- const partition = (n_out, n_src) => Array.from({ length: n_out }, (_, idx) => [
107
- Math.round((idx * n_src) / n_out),
108
- Math.round(((idx + 1) * n_src) / n_out),
109
- ]);
110
- const x_ranges = partition(new_nx, nx);
111
- const y_ranges = partition(new_ny, ny);
112
- const z_ranges = partition(new_nz, nz);
110
+ const x_ranges = partition_ranges(new_nx, nx);
111
+ const y_ranges = partition_ranges(new_ny, ny);
112
+ const z_ranges = partition_ranges(new_nz, nz);
113
113
  const out = Array(new_nx);
114
114
  for (let ix = 0; ix < new_nx; ix++) {
115
115
  const plane = Array(new_ny);
package/dist/labels.d.ts CHANGED
@@ -5,7 +5,7 @@ import * as d3_symbols from 'd3-shape';
5
5
  export type D3Symbol = keyof typeof d3_symbols & `symbol${Capitalize<string>}`;
6
6
  export type D3SymbolName = Exclude<D3Symbol extends `symbol${infer Name}` ? Name : never, ``>;
7
7
  export declare const symbol_names: D3SymbolName[];
8
- export declare const symbol_map: Record<D3SymbolName, SymbolType>;
8
+ export declare const symbol_map: Partial<Record<D3SymbolName, SymbolType>>;
9
9
  export declare function format_value(value: number, formatter?: string): string;
10
10
  export declare const ELEM_PROPERTY_LABELS: Partial<Record<keyof ChemicalElement, [string, string | null]>>;
11
11
  export declare const ELEM_HEATMAP_KEYS: (keyof ChemicalElement)[];
package/dist/labels.js CHANGED
@@ -1,12 +1,14 @@
1
1
  import { format } from 'd3-format';
2
2
  import * as d3_symbols from 'd3-shape';
3
3
  import { timeFormat } from 'd3-time-format';
4
+ const is_d3_symbol_name = (name) => Object.hasOwn(d3_symbols, `symbol${name}`);
4
5
  function name_for_symbol(sym) {
5
- for (const key in d3_symbols) {
6
- if (Object.prototype.hasOwnProperty.call(d3_symbols, key) &&
7
- d3_symbols[key] === sym &&
8
- /^symbol[A-Z]/.test(key))
9
- return key.substring(6);
6
+ for (const [key, symbol] of Object.entries(d3_symbols)) {
7
+ if (symbol === sym && /^symbol[A-Z]/.test(key)) {
8
+ const name = key.substring(6);
9
+ if (is_d3_symbol_name(name))
10
+ return name;
11
+ }
10
12
  }
11
13
  return null;
12
14
  }
@@ -81,7 +83,7 @@ export const ELEM_HEATMAP_KEYS = [
81
83
  export const ELEM_HEATMAP_LABELS = Object.fromEntries(ELEM_HEATMAP_KEYS.map((key) => {
82
84
  const [label, unit] = ELEM_PROPERTY_LABELS[key] ?? [];
83
85
  if (!label)
84
- throw `Unexpected missing label ${label}`;
86
+ throw new Error(`Unexpected missing label for element property ${key}`);
85
87
  return [label + (unit ? ` (${unit})` : ``), key];
86
88
  }));
87
89
  // Allow users to import DEFAULT_FMT and change its items in place to
@@ -134,18 +136,18 @@ export const format_bytes = (bytes) => {
134
136
  export function format_fractional(value) {
135
137
  if (!Number.isFinite(value))
136
138
  return String(value);
137
- const x = ((value % 1) + 1) % 1; // wrap into [0,1)
139
+ const wrapped_value = ((value % 1) + 1) % 1; // wrap into [0,1)
138
140
  const eps = 1e-3;
139
141
  for (const [target, glyph] of FRACTION_GLYPHS) {
140
142
  if (target === 0) {
141
- if (Math.abs(x - target) <= eps)
143
+ if (Math.abs(wrapped_value - target) <= eps)
142
144
  return glyph;
143
145
  }
144
- else if (Math.abs(x - target) < eps)
146
+ else if (Math.abs(wrapped_value - target) < eps)
145
147
  return glyph;
146
148
  }
147
149
  for (const [target, glyph] of FRACTION_GLYPHS) {
148
- if (target !== 0 && Math.abs(1 - x - target) < eps)
150
+ if (target !== 0 && Math.abs(1 - wrapped_value - target) < eps)
149
151
  return glyph;
150
152
  }
151
153
  return format_num(value, `.4~`);
@@ -216,6 +218,7 @@ export const SUPERSCRIPT_MAP = {
216
218
  '+': `⁺`,
217
219
  '-': `⁻`,
218
220
  };
221
+ const is_superscript_key = (key) => key in SUPERSCRIPT_MAP;
219
222
  export const SUBSCRIPT_MAP = {
220
223
  '0': `₀`,
221
224
  '1': `₁`,
@@ -229,7 +232,7 @@ export const SUBSCRIPT_MAP = {
229
232
  '9': `₉`,
230
233
  };
231
234
  // replaces all signs and digits with their unicode superscript equivalent
232
- export const superscript_digits = (input) => input.replace(/[\d+-]/g, (match) => SUPERSCRIPT_MAP[match] ?? match);
235
+ export const superscript_digits = (input) => input.replace(/[\d+-]/g, (match) => is_superscript_key(match) ? SUPERSCRIPT_MAP[match] : match);
233
236
  // Trajectory property configuration: clean labels and units as structured data
234
237
  export const trajectory_property_config = {
235
238
  // Energy properties
@@ -49,10 +49,12 @@
49
49
  else void copy_to_clipboard()
50
50
  }
51
51
 
52
- function handle_keydown(event: KeyboardEvent): void {
52
+ function handle_keydown(
53
+ event: KeyboardEvent & { currentTarget: HTMLElement },
54
+ ): void {
53
55
  if (disabled || (event.key !== `Enter` && event.key !== ` `)) return
54
56
  event.preventDefault()
55
- ;(event.currentTarget as HTMLElement)?.click()
57
+ event.currentTarget.click()
56
58
  }
57
59
 
58
60
  function handle_remove(event: MouseEvent): void {
@@ -66,8 +68,8 @@
66
68
  tabindex={disabled ? -1 : 0}
67
69
  onclick={handle_click}
68
70
  onkeydown={handle_keydown}
69
- {title}
70
- {@attach tooltip()}
71
+ title={sanitize_html(title)}
72
+ {@attach tooltip({ allow_html: true })}
71
73
  class="info-tag {variant} {size}"
72
74
  class:disabled
73
75
  aria-disabled={disabled}
@@ -56,10 +56,12 @@
56
56
  return histogram_data.filter((val) => val >= min && val <= max)
57
57
  })
58
58
 
59
- function onkeydown(event: KeyboardEvent): void {
59
+ function onkeydown(
60
+ event: KeyboardEvent & { currentTarget: HTMLInputElement },
61
+ ): void {
60
62
  if (event.key === `Enter`) {
61
63
  event.preventDefault()
62
- ;(event.target as HTMLInputElement).blur()
64
+ event.currentTarget.blur()
63
65
  } else if (event.key === `Escape` && active) {
64
66
  event.preventDefault()
65
67
  clear_filter()
@@ -1,6 +1,7 @@
1
1
  <script lang="ts">
2
2
  import Icon from '../../Icon.svelte'
3
3
  import { download } from '../../io/fetch'
4
+ import { make_change_detector } from '../../utils'
4
5
  import { setContext, tick } from 'svelte'
5
6
  import { highlight_matches, tooltip } from 'svelte-multiselect/attachments'
6
7
  import type { HTMLAttributes } from 'svelte/elements'
@@ -89,24 +90,31 @@
89
90
  // Copy feedback positioning (null = use default corner position)
90
91
  let copy_feedback_pos = $state<{ x: number; y: number } | null>(null)
91
92
 
92
- // Clear stale path-based state when value changes
93
- let prev_value_ref: unknown
93
+ const value_changed = make_change_detector()
94
94
  $effect.pre(() => {
95
- if (prev_value_ref !== undefined && value !== prev_value_ref) {
96
- force_expanded.clear()
97
- pinned_paths = new SvelteSet()
98
- selected_paths = new SvelteSet()
99
- last_selected_path = null
100
- }
101
- prev_value_ref = value
95
+ if (!value_changed(value)) return
96
+ focused_path = null
97
+ registered_paths_set = new Set()
98
+ registered_paths_list = []
99
+ copy_feedback_path = null
100
+ copy_feedback_error = false
101
+ context_menu_state = null
102
+ force_expanded = new SvelteSet()
103
+ const valid_paths = new SvelteSet(collect_all_paths(value, root_label ?? ``))
104
+ collapsed_paths = new SvelteSet([...collapsed_paths].filter((path) => valid_paths.has(path)))
105
+ pinned_paths = new SvelteSet()
106
+ selected_paths = new SvelteSet()
107
+ last_selected_path = null
108
+ current_match_index = -1
109
+ prev_values.clear()
110
+ if (search_query) queueMicrotask(() => expand_to_matches())
102
111
  })
103
112
 
104
113
  // Debounce search input
105
114
  let search_debounce_timeout: ReturnType<typeof setTimeout> | undefined
106
115
 
107
- function handle_search_input(event: Event) {
108
- const input = event.target as HTMLInputElement
109
- search_input_value = input.value
116
+ function handle_search_input(event: Event & { currentTarget: HTMLInputElement }) {
117
+ search_input_value = event.currentTarget.value
110
118
 
111
119
  if (search_debounce_timeout) clearTimeout(search_debounce_timeout)
112
120
  search_debounce_timeout = setTimeout(() => {
@@ -209,7 +217,7 @@
209
217
  }
210
218
 
211
219
  // Previous values map for change detection
212
- const previous_values = new Map<string, unknown>()
220
+ const prev_values = new Map<string, unknown>()
213
221
 
214
222
  // Toggle collapse - tracks force_expanded to override auto-fold thresholds
215
223
  function toggle_collapse(path: string, is_currently_collapsed: boolean): void {
@@ -471,7 +479,7 @@
471
479
  get focused_path() {
472
480
  return focused_path
473
481
  },
474
- previous_values,
482
+ prev_values,
475
483
  toggle_collapse,
476
484
  toggle_collapse_recursive,
477
485
  expand_all,
@@ -39,7 +39,7 @@
39
39
  $effect(() => {
40
40
  if (!ctx?.settings.highlight_changes) return
41
41
 
42
- const prev = ctx.previous_values.get(path)
42
+ const prev = ctx.prev_values.get(path)
43
43
  if (prev !== undefined && !values_equal(prev, value)) {
44
44
  just_changed = true
45
45
  if (change_timeout) clearTimeout(change_timeout)
@@ -47,7 +47,7 @@
47
47
  just_changed = false
48
48
  }, 1000)
49
49
  }
50
- ctx.previous_values.set(path, value)
50
+ ctx.prev_values.set(path, value)
51
51
 
52
52
  return () => {
53
53
  if (change_timeout) clearTimeout(change_timeout)
@@ -1,3 +1,4 @@
1
+ import type { GhostEntry } from './utils';
1
2
  export type JsonValueType = `string` | `number` | `boolean` | `null` | `undefined` | `object` | `array` | `date` | `regexp` | `map` | `set` | `symbol` | `bigint` | `error` | `function` | `circular`;
2
3
  export interface JsonTreeProps {
3
4
  value: unknown;
@@ -37,7 +38,7 @@ export interface JsonTreeContext {
37
38
  search_matches: Set<string>;
38
39
  current_match_path: string | null;
39
40
  focused_path: string | null;
40
- previous_values: Map<string, unknown>;
41
+ prev_values: Map<string, unknown>;
41
42
  toggle_collapse: (path: string, is_currently_collapsed: boolean) => void;
42
43
  toggle_collapse_recursive: (path: string, collapse: boolean) => void;
43
44
  expand_all: () => void;
@@ -55,7 +56,7 @@ export interface JsonTreeContext {
55
56
  toggle_select: (path: string, shift: boolean) => void;
56
57
  copy_selected: () => void;
57
58
  diff_map: Map<string, DiffEntry> | null;
58
- ghost_map: Map<string, import('./utils').GhostEntry[]>;
59
+ ghost_map: Map<string, GhostEntry[]>;
59
60
  collapse_children_only: (path: string) => void;
60
61
  onchange?: (path: string, new_value: unknown, old_value: unknown) => void;
61
62
  }
@@ -1,3 +1,2 @@
1
- // JSON Tree component types
2
1
  // Context key for Svelte's setContext/getContext
3
2
  export const JSON_TREE_CONTEXT_KEY = Symbol(`json-tree-context`);
@@ -1,8 +1,8 @@
1
1
  import type { DiffEntry, JsonValueType } from './types';
2
2
  export declare function get_value_type(value: unknown): JsonValueType;
3
- export declare function is_expandable_type(value_type: JsonValueType): boolean;
4
- export declare function is_primitive_type(value_type: JsonValueType): boolean;
5
- export declare function is_expandable(value: unknown): boolean;
3
+ export declare const is_expandable_type: (value_type: JsonValueType) => boolean;
4
+ export declare const is_primitive_type: (value_type: JsonValueType) => boolean;
5
+ export declare const is_expandable: (value: unknown) => boolean;
6
6
  export declare function get_child_count(value: unknown): number;
7
7
  export declare function format_path(segments: (string | number)[]): string;
8
8
  export declare function build_path(parent_path: string, key: string | number): string;
@@ -16,7 +16,7 @@ export declare function parse_path(path: string): (string | number)[];
16
16
  export declare function values_equal(val_a: unknown, val_b: unknown): boolean;
17
17
  export declare function parse_edited_value(text: string): unknown;
18
18
  export declare function set_at_path(root: unknown, path_str: string, new_value: unknown, root_label?: string): unknown;
19
- export declare function is_url(str: string): boolean;
19
+ export declare const is_url: (str: string) => boolean;
20
20
  export declare function is_css_color(str: string): boolean;
21
21
  export declare function estimate_byte_size(value: unknown, max_depth?: number, current_depth?: number): number;
22
22
  export interface GhostEntry {