matterviz 0.4.2 → 0.4.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 (244) hide show
  1. package/dist/FilePicker.svelte +40 -50
  2. package/dist/api/optimade.js +10 -7
  3. package/dist/app.css +15 -0
  4. package/dist/brillouin/BrillouinZone.svelte +18 -9
  5. package/dist/brillouin/BrillouinZoneScene.svelte +20 -29
  6. package/dist/brillouin/compute.js +10 -16
  7. package/dist/brillouin/geometry.js +2 -2
  8. package/dist/chempot-diagram/ChemPotDiagram.svelte +18 -15
  9. package/dist/chempot-diagram/ChemPotDiagram2D.svelte +17 -113
  10. package/dist/chempot-diagram/ChemPotDiagram3D.svelte +104 -264
  11. package/dist/chempot-diagram/async-compute.svelte.js +12 -13
  12. package/dist/chempot-diagram/color.d.ts +21 -1
  13. package/dist/chempot-diagram/color.js +70 -1
  14. package/dist/chempot-diagram/compute.d.ts +7 -0
  15. package/dist/chempot-diagram/compute.js +85 -26
  16. package/dist/chempot-diagram/export.js +1 -6
  17. package/dist/chempot-diagram/temperature.js +9 -10
  18. package/dist/composition/BarChart.svelte +7 -9
  19. package/dist/composition/Composition.svelte +3 -8
  20. package/dist/composition/Formula.svelte +2 -2
  21. package/dist/composition/FormulaFilter.svelte +27 -93
  22. package/dist/composition/format.js +6 -8
  23. package/dist/composition/parse.js +50 -105
  24. package/dist/constants.d.ts +0 -1
  25. package/dist/constants.js +6 -17
  26. package/dist/convex-hull/ConvexHull2D.svelte +0 -2
  27. package/dist/convex-hull/ConvexHull3D.svelte +19 -47
  28. package/dist/convex-hull/ConvexHull4D.svelte +65 -95
  29. package/dist/convex-hull/StructurePopup.svelte +1 -1
  30. package/dist/convex-hull/gas-thermodynamics.js +21 -53
  31. package/dist/convex-hull/helpers.js +1 -7
  32. package/dist/convex-hull/thermodynamics.js +19 -27
  33. package/dist/coordination/CoordinationBarPlot.svelte +12 -21
  34. package/dist/element/BohrAtom.svelte +0 -1
  35. package/dist/element/ElementTile.svelte +26 -44
  36. package/dist/element/data.d.ts +2 -0
  37. package/dist/element/data.js +5 -1
  38. package/dist/element/index.d.ts +1 -1
  39. package/dist/element/index.js +1 -1
  40. package/dist/fermi-surface/FermiSurface.svelte +22 -23
  41. package/dist/fermi-surface/FermiSurface.svelte.d.ts +2 -6
  42. package/dist/fermi-surface/FermiSurfaceScene.svelte +32 -77
  43. package/dist/fermi-surface/compute.js +22 -55
  44. package/dist/fermi-surface/parse.js +38 -70
  45. package/dist/file-viewer/JsonBrowser.svelte +1043 -0
  46. package/dist/file-viewer/JsonBrowser.svelte.d.ts +9 -0
  47. package/dist/file-viewer/PlotPanel.svelte +350 -0
  48. package/dist/file-viewer/PlotPanel.svelte.d.ts +9 -0
  49. package/dist/file-viewer/TrajectoryWithDos.svelte +30 -0
  50. package/dist/file-viewer/TrajectoryWithDos.svelte.d.ts +10 -0
  51. package/dist/file-viewer/detect.d.ts +11 -0
  52. package/dist/file-viewer/detect.js +381 -0
  53. package/dist/file-viewer/eligibility.d.ts +4 -0
  54. package/dist/file-viewer/eligibility.js +53 -0
  55. package/dist/file-viewer/host-protocol.d.ts +50 -0
  56. package/dist/file-viewer/host-transfer.d.ts +25 -0
  57. package/dist/file-viewer/host-transfer.js +42 -0
  58. package/dist/file-viewer/index.d.ts +5 -0
  59. package/dist/file-viewer/index.js +5 -0
  60. package/dist/file-viewer/main.d.ts +44 -0
  61. package/dist/file-viewer/main.js +535 -0
  62. package/dist/file-viewer/parse.d.ts +9 -0
  63. package/dist/file-viewer/parse.js +170 -0
  64. package/dist/file-viewer/plot-utils.d.ts +24 -0
  65. package/dist/file-viewer/plot-utils.js +191 -0
  66. package/dist/file-viewer/types.d.ts +4 -0
  67. package/dist/file-viewer/types.js +5 -0
  68. package/dist/heatmap-matrix/HeatmapMatrix.svelte +57 -84
  69. package/dist/heatmap-matrix/HeatmapMatrixControls.svelte.d.ts +1 -1
  70. package/dist/index.d.ts +2 -2
  71. package/dist/index.js +1 -1
  72. package/dist/io/ExportPane.svelte +1 -1
  73. package/dist/io/decompress.d.ts +2 -0
  74. package/dist/io/decompress.js +3 -2
  75. package/dist/io/export.d.ts +7 -4
  76. package/dist/io/export.js +70 -18
  77. package/dist/io/file-drop.js +60 -35
  78. package/dist/io/url-drop.d.ts +2 -0
  79. package/dist/io/url-drop.js +28 -16
  80. package/dist/isosurface/Isosurface.svelte +687 -196
  81. package/dist/isosurface/Isosurface.svelte.d.ts +7 -1
  82. package/dist/isosurface/IsosurfaceControls.svelte +564 -85
  83. package/dist/isosurface/IsosurfaceControls.svelte.d.ts +1 -1
  84. package/dist/isosurface/VolumeSlice.svelte +224 -0
  85. package/dist/isosurface/VolumeSlice.svelte.d.ts +28 -0
  86. package/dist/isosurface/coloring.d.ts +20 -0
  87. package/dist/isosurface/coloring.js +118 -0
  88. package/dist/isosurface/geometry-worker-types.d.ts +38 -0
  89. package/dist/isosurface/geometry.worker.d.ts +1 -0
  90. package/dist/isosurface/geometry.worker.js +59 -0
  91. package/dist/isosurface/grid.d.ts +10 -0
  92. package/dist/isosurface/grid.js +31 -0
  93. package/dist/isosurface/index.d.ts +4 -0
  94. package/dist/isosurface/index.js +4 -0
  95. package/dist/isosurface/profile.d.ts +12 -0
  96. package/dist/isosurface/profile.js +19 -0
  97. package/dist/isosurface/sampling.d.ts +34 -0
  98. package/dist/isosurface/sampling.js +408 -0
  99. package/dist/isosurface/slice-rendering.d.ts +13 -0
  100. package/dist/isosurface/slice-rendering.js +79 -0
  101. package/dist/isosurface/slice.d.ts +22 -2
  102. package/dist/isosurface/slice.js +200 -116
  103. package/dist/isosurface/types.d.ts +24 -1
  104. package/dist/isosurface/types.js +120 -8
  105. package/dist/json-path.d.ts +4 -0
  106. package/dist/json-path.js +89 -0
  107. package/dist/labels.js +1 -1
  108. package/dist/layout/InfoCard.svelte +1 -3
  109. package/dist/layout/SettingsSection.svelte +52 -61
  110. package/dist/layout/SettingsSection.svelte.d.ts +6 -7
  111. package/dist/layout/ViewerChrome.svelte +7 -1
  112. package/dist/layout/json-tree/JsonNode.svelte +14 -48
  113. package/dist/layout/json-tree/JsonTree.svelte +48 -95
  114. package/dist/layout/json-tree/JsonValue.svelte +3 -18
  115. package/dist/layout/json-tree/utils.d.ts +0 -3
  116. package/dist/layout/json-tree/utils.js +50 -179
  117. package/dist/marching-cubes.d.ts +8 -1
  118. package/dist/marching-cubes.js +115 -48
  119. package/dist/math.d.ts +3 -2
  120. package/dist/math.js +29 -38
  121. package/dist/overlays/ContextMenu.svelte +15 -32
  122. package/dist/overlays/DraggablePane.svelte +39 -58
  123. package/dist/overlays/DraggablePane.svelte.d.ts +1 -1
  124. package/dist/overlays/GlassChip.svelte +3 -1
  125. package/dist/periodic-table/PeriodicTableControls.svelte +26 -52
  126. package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte +18 -34
  127. package/dist/phase-diagram/build-diagram.js +1 -6
  128. package/dist/phase-diagram/svg-to-diagram.js +9 -14
  129. package/dist/plot/bar/BarPlot.svelte +38 -42
  130. package/dist/plot/bar/SpacegroupBarPlot.svelte +18 -42
  131. package/dist/plot/box/BoxPlot.svelte +27 -23
  132. package/dist/plot/core/auto-place.d.ts +1 -1
  133. package/dist/plot/core/auto-place.js +1 -1
  134. package/dist/plot/core/data-transform.d.ts +1 -2
  135. package/dist/plot/core/data-transform.js +0 -10
  136. package/dist/plot/core/layout.d.ts +12 -5
  137. package/dist/plot/core/layout.js +37 -27
  138. package/dist/plot/core/pan-zoom.svelte.d.ts +2 -4
  139. package/dist/plot/core/pan-zoom.svelte.js +34 -17
  140. package/dist/plot/core/placed-tween.svelte.d.ts +3 -0
  141. package/dist/plot/core/placed-tween.svelte.js +68 -21
  142. package/dist/plot/core/svg.js +0 -1
  143. package/dist/plot/core/utils/hierarchy-chart.d.ts +1 -0
  144. package/dist/plot/core/utils/hierarchy-chart.js +12 -0
  145. package/dist/plot/core/utils/hierarchy-labels.js +3 -2
  146. package/dist/plot/histogram/Histogram.svelte +21 -26
  147. package/dist/plot/sankey/sankey.js +13 -9
  148. package/dist/plot/scatter/BinnedScatterPlot.svelte +59 -43
  149. package/dist/plot/scatter/ScatterPlot.svelte +67 -52
  150. package/dist/plot/scatter/adaptive-density.d.ts +1 -1
  151. package/dist/plot/scatter/adaptive-density.js +36 -20
  152. package/dist/plot/sunburst/Sunburst.svelte +20 -20
  153. package/dist/plot/treemap/Treemap.svelte +175 -60
  154. package/dist/plot/treemap/Treemap.svelte.d.ts +6 -0
  155. package/dist/plot/treemap/index.d.ts +1 -0
  156. package/dist/plot/treemap/labels.d.ts +38 -0
  157. package/dist/plot/treemap/labels.js +80 -0
  158. package/dist/plot/treemap/treemap.d.ts +4 -2
  159. package/dist/plot/treemap/treemap.js +5 -2
  160. package/dist/rdf/RdfPlot.svelte +17 -35
  161. package/dist/rdf/calc-rdf.js +59 -44
  162. package/dist/rdf/index.d.ts +0 -1
  163. package/dist/sanitize.js +84 -26
  164. package/dist/scene/SceneCamera.svelte +1 -1
  165. package/dist/scene/SceneCamera.svelte.d.ts +1 -1
  166. package/dist/scene/index.d.ts +1 -2
  167. package/dist/scene/index.js +1 -1
  168. package/dist/scene/{props.js → props.svelte.js} +15 -1
  169. package/dist/settings.js +159 -482
  170. package/dist/spectral/Bands.svelte +151 -158
  171. package/dist/spectral/BrillouinBandsDos.svelte +4 -19
  172. package/dist/spectral/Dos.svelte +26 -24
  173. package/dist/spectral/helpers.js +40 -105
  174. package/dist/structure/AtomLegend.svelte +8 -13
  175. package/dist/structure/AtomLegend.svelte.d.ts +2 -3
  176. package/dist/structure/CellSelect.svelte +35 -10
  177. package/dist/structure/Structure.svelte +177 -99
  178. package/dist/structure/Structure.svelte.d.ts +1 -1
  179. package/dist/structure/StructureCarousel.svelte +10 -3
  180. package/dist/structure/StructureControls.svelte +8 -26
  181. package/dist/structure/StructureControls.svelte.d.ts +1 -1
  182. package/dist/structure/StructureInfoPane.svelte +65 -76
  183. package/dist/structure/StructureScene.svelte +75 -109
  184. package/dist/structure/StructureScene.svelte.d.ts +3 -1
  185. package/dist/structure/StructureViewport.svelte +120 -55
  186. package/dist/structure/StructureViewport.svelte.d.ts +3 -1
  187. package/dist/structure/atom-properties.d.ts +1 -1
  188. package/dist/structure/atom-properties.js +4 -3
  189. package/dist/structure/bond-order-perception.js +3 -8
  190. package/dist/structure/bonding.d.ts +0 -1
  191. package/dist/structure/bonding.js +16 -23
  192. package/dist/structure/export.js +50 -74
  193. package/dist/structure/format-detect.js +5 -7
  194. package/dist/structure/index.js +6 -12
  195. package/dist/structure/parse.js +38 -100
  196. package/dist/structure/partial-occupancy.js +7 -4
  197. package/dist/structure/pbc.js +8 -10
  198. package/dist/structure/polyhedra.js +5 -4
  199. package/dist/structure/prop-groups.d.ts +85 -0
  200. package/dist/structure/prop-groups.js +18 -0
  201. package/dist/symmetry/SymmetryStats.svelte +0 -4
  202. package/dist/symmetry/WyckoffTable.svelte +5 -4
  203. package/dist/symmetry/cell-transform.js +5 -14
  204. package/dist/symmetry/index.js +6 -9
  205. package/dist/table/HeatmapTable.svelte +61 -103
  206. package/dist/table/ToggleMenu.svelte +166 -44
  207. package/dist/table/ToggleMenu.svelte.d.ts +1 -1
  208. package/dist/theme/embedded.d.ts +18 -0
  209. package/dist/theme/embedded.js +206 -0
  210. package/dist/theme/index.d.ts +4 -0
  211. package/dist/theme/index.js +1 -2
  212. package/dist/theme/themes.mjs +31 -60
  213. package/dist/tooltip/TooltipContent.svelte +1 -1
  214. package/dist/tooltip/TooltipContent.svelte.d.ts +1 -1
  215. package/dist/tooltip/index.d.ts +8 -1
  216. package/dist/trajectory/Trajectory.svelte +140 -120
  217. package/dist/trajectory/TrajectoryError.svelte +1 -66
  218. package/dist/trajectory/TrajectoryExportPane.svelte +0 -1
  219. package/dist/trajectory/TrajectoryInfoPane.svelte +55 -80
  220. package/dist/trajectory/TrajectoryInfoPane.svelte.d.ts +2 -1
  221. package/dist/trajectory/extract.js +19 -29
  222. package/dist/trajectory/format-detect.d.ts +2 -0
  223. package/dist/trajectory/format-detect.js +7 -5
  224. package/dist/trajectory/frame-reader.js +3 -4
  225. package/dist/trajectory/helpers.js +16 -49
  226. package/dist/trajectory/index.js +2 -8
  227. package/dist/trajectory/parse/ase.js +1 -1
  228. package/dist/trajectory/parse/index.d.ts +4 -3
  229. package/dist/trajectory/parse/index.js +12 -17
  230. package/dist/trajectory/parse/vaspout-h5.d.ts +0 -1
  231. package/dist/trajectory/parse/vaspout-h5.js +1 -4
  232. package/dist/trajectory/parse/xyz.js +48 -2
  233. package/dist/trajectory/plotting.js +14 -37
  234. package/dist/xrd/XrdPlot.svelte +43 -68
  235. package/dist/xrd/calc-xrd.js +22 -58
  236. package/dist/xrd/parse.js +13 -22
  237. package/package.json +40 -8
  238. package/readme.md +6 -5
  239. package/dist/scene/types.d.ts +0 -26
  240. package/dist/tooltip/types.d.ts +0 -8
  241. package/dist/trajectory/constants.d.ts +0 -6
  242. package/dist/trajectory/constants.js +0 -11
  243. /package/dist/{scene/types.js → file-viewer/host-protocol.js} +0 -0
  244. /package/dist/{tooltip/types.js → isosurface/geometry-worker-types.js} +0 -0
@@ -16,13 +16,16 @@
16
16
  import PlotMarginals from '../core/components/PlotMarginals.svelte'
17
17
  import PlotTooltip from '../core/components/PlotTooltip.svelte'
18
18
  import ZoomRect from '../core/components/ZoomRect.svelte'
19
+ import { sorted_range } from '../core/interactions'
20
+ import { create_placed_tween } from '../core/placed-tween.svelte'
19
21
  import {
20
22
  AXIS_TITLE_OFFSET,
21
23
  compute_element_placement,
22
24
  DEFAULT_PLOT_PADDING,
23
25
  filter_padding,
26
+ full_footprint_or,
24
27
  LABEL_GAP_DEFAULT,
25
- measure_full_footprint,
28
+ point_in_rect,
26
29
  } from '../core/layout'
27
30
  import type { Sides } from '../core/layout'
28
31
  import { get_series_color } from '../core/data-transform'
@@ -161,14 +164,14 @@
161
164
  let x_range = $state<Vec2>([0, 1])
162
165
  let y_range = $state<Vec2>([0, 1])
163
166
  let has_user_range = $state(false)
164
- let drag_start = $state<Point2D | null>(null)
165
- let drag_current = $state<Point2D | null>(null)
167
+ let drag_state = $state<{ start: Point2D; current: Point2D } | null>(null)
166
168
  let suppress_next_click = false
167
169
  let hovered_bin = $state<DensityBin | null>(null)
168
170
  let hovered_point = $state<DenseInternalPoint<Metadata> | null>(null)
169
171
  let tooltip_pos = $state<Point2D>({ x: 0, y: 0 })
170
172
  let colorbar_element = $state<HTMLDivElement>()
171
173
  let annotation_element = $state<HTMLDivElement>()
174
+ let colorbar_size_revision = $state(0)
172
175
  let label_measure_root = $state<HTMLDivElement>()
173
176
  let label_sizes = new SvelteMap<string, LabelSize>()
174
177
  const clip_path_id = `binned-scatter-plot-area-${next_clip_id++}`
@@ -263,14 +266,16 @@
263
266
  const needs_data_range = (range: AxisConfig[`range`] | undefined): boolean =>
264
267
  range?.[0] == null || range?.[1] == null
265
268
 
269
+ let x_scale_type = $derived(x_axis.scale_type ?? `linear`)
270
+ let y_scale_type = $derived(y_axis.scale_type ?? `linear`)
266
271
  let needs_auto_range = $derived(
267
272
  needs_data_range(x_axis.range) || needs_data_range(y_axis.range),
268
273
  )
269
274
  let auto_ranges = $derived(
270
- needs_auto_range ? series_extents(series) : { x: [0, 1] as Vec2, y: [0, 1] as Vec2 },
275
+ needs_auto_range
276
+ ? series_extents(series, x_scale_type, y_scale_type)
277
+ : { x: [0, 1] as Vec2, y: [0, 1] as Vec2 },
271
278
  )
272
- let x_scale_type = $derived(x_axis.scale_type ?? `linear`)
273
- let y_scale_type = $derived(y_axis.scale_type ?? `linear`)
274
279
  let has_plot_size = $derived(width > 0 && height > 0)
275
280
 
276
281
  const axis_range = (axis: AxisConfig, fallback: Vec2): Vec2 => [
@@ -383,7 +388,7 @@
383
388
  ? COLOR_BAR_DEFAULTS.vertical_footprint
384
389
  : COLOR_BAR_DEFAULTS.horizontal_footprint,
385
390
  )
386
- let color_bar_placement = $derived.by(() => {
391
+ const get_color_bar_placement = () => {
387
392
  if (
388
393
  !color_bar_props ||
389
394
  render_mode !== `density` ||
@@ -404,21 +409,23 @@
404
409
  points: density_placement_points,
405
410
  grid_resolution: 12,
406
411
  })
407
- })
412
+ }
408
413
  // Fallback footprint before the annotation snippet first renders; the real
409
414
  // footprint is measured once laid out (mirrors colorbar_fallback_size above)
410
415
  const annotation_fallback_size = { width: 120, height: 50 }
411
416
  // Auto-place the annotation snippet like the colorbar, but with the already-placed
412
417
  // colorbar's footprint as an exclusion zone so the two never overlap.
413
- let annotation_placement = $derived.by(() => {
418
+ const get_annotation_placement = () => {
414
419
  if (!annotation || !width || !height) return null
415
420
 
421
+ const color_bar_placement = colorbar_tween.placed()
422
+ ? colorbar_tween.coords.target
423
+ : get_color_bar_placement()
424
+ const colorbar_footprint = full_footprint_or(colorbar_element, colorbar_fallback_size)
416
425
  const colorbar_rect = color_bar_placement && {
417
- ...(colorbar_element?.offsetWidth && colorbar_element?.offsetHeight
418
- ? measure_full_footprint(colorbar_element)
419
- : colorbar_fallback_size),
420
- x: color_bar_placement.x,
421
- y: color_bar_placement.y,
426
+ ...colorbar_footprint,
427
+ x: color_bar_placement.x + colorbar_footprint.offset_x,
428
+ y: color_bar_placement.y + colorbar_footprint.offset_y,
422
429
  }
423
430
  return compute_element_placement({
424
431
  plot_bounds: plot_rect,
@@ -429,6 +436,21 @@
429
436
  points: density_placement_points,
430
437
  grid_resolution: 12,
431
438
  })
439
+ }
440
+ const colorbar_tween = create_placed_tween({
441
+ placement: get_color_bar_placement,
442
+ dims: () => ({ width, height }),
443
+ responsive: () => false,
444
+ element: () => colorbar_element,
445
+ on_element_resize: () => (colorbar_size_revision += 1),
446
+ })
447
+ const annotation_tween = create_placed_tween({
448
+ placement: get_annotation_placement,
449
+ dims: () => ({ width, height }),
450
+ responsive: () => false,
451
+ element: () => annotation_element,
452
+ placement_revision: () =>
453
+ `${colorbar_size_revision}:${colorbar_tween.coords.target.x}:${colorbar_tween.coords.target.y}`,
432
454
  })
433
455
 
434
456
  let auto_render_mode = $derived.by((): RenderMode => {
@@ -597,8 +619,9 @@
597
619
  })
598
620
 
599
621
  function pointer_coords(event: PointerEvent | MouseEvent): Point2D | null {
600
- if (!wrapper) return null
601
- const rect = wrapper.getBoundingClientRect()
622
+ if (!canvas) return null
623
+ // The fullscreen wrapper has a top border outside the canvas coordinate space.
624
+ const rect = canvas.getBoundingClientRect()
602
625
  return { x: event.clientX - rect.left, y: event.clientY - rect.top }
603
626
  }
604
627
 
@@ -854,43 +877,36 @@
854
877
  function on_pointer_down(event: PointerEvent) {
855
878
  if (event.button !== 0) return
856
879
  const coords = pointer_coords(event)
857
- if (!coords) return
858
- drag_start = coords
859
- drag_current = coords
880
+ if (!coords || !point_in_rect(coords, plot_rect)) return
881
+ drag_state = { start: coords, current: coords }
860
882
  if (event.currentTarget instanceof HTMLElement) {
861
883
  event.currentTarget.setPointerCapture?.(event.pointerId)
862
884
  }
863
885
  }
864
886
 
865
887
  function on_pointer_drag(event: PointerEvent) {
866
- if (!drag_start) {
888
+ if (!drag_state) {
867
889
  on_pointer_move(event)
868
890
  return
869
891
  }
870
892
  const coords = pointer_coords(event)
871
- if (coords) drag_current = coords
893
+ if (coords) drag_state.current = coords
872
894
  }
873
895
 
874
896
  function on_pointer_up(event: PointerEvent) {
875
- const start = drag_start
876
- const end = drag_current
877
- drag_start = null
878
- drag_current = null
879
-
880
- if (start && end && Math.abs(end.x - start.x) > 5 && Math.abs(end.y - start.y) > 5) {
881
- const x0 = x_scale_fn.invert(start.x)
882
- const x1 = x_scale_fn.invert(end.x)
883
- const y0 = y_scale_fn.invert(start.y)
884
- const y1 = y_scale_fn.invert(end.y)
885
- x_range = [Math.min(x0, x1), Math.max(x0, x1)]
886
- y_range = [Math.min(y0, y1), Math.max(y0, y1)]
887
- has_user_range = true
888
- suppress_next_click = true
889
- }
897
+ if (!drag_state) return
898
+ const { start, current: end } = drag_state
899
+ drag_state = null
890
900
 
891
901
  if (event.currentTarget instanceof HTMLElement) {
892
902
  event.currentTarget.releasePointerCapture?.(event.pointerId)
893
903
  }
904
+ if (Math.abs(end.x - start.x) <= 5 || Math.abs(end.y - start.y) <= 5) return
905
+
906
+ x_range = sorted_range(x_scale_fn.invert(start.x), x_scale_fn.invert(end.x))
907
+ y_range = sorted_range(y_scale_fn.invert(start.y), y_scale_fn.invert(end.y))
908
+ has_user_range = true
909
+ suppress_next_click = true
894
910
  }
895
911
 
896
912
  onMount(() => {
@@ -1004,7 +1020,7 @@
1004
1020
  label_y={pad.t + plot_height / 2}
1005
1021
  />
1006
1022
 
1007
- <ZoomRect start={drag_start} current={drag_current} />
1023
+ <ZoomRect start={drag_state?.start ?? null} current={drag_state?.current ?? null} />
1008
1024
 
1009
1025
  {#if point_label_payloads.length}
1010
1026
  <g class="point-label-leaders" clip-path="url(#{clip_path_id})">
@@ -1071,12 +1087,12 @@
1071
1087
  </div>
1072
1088
  {/if}
1073
1089
 
1074
- {#if color_bar_props && render_mode === `density` && density_result.max_count > 0 && color_bar_placement}
1090
+ {#if width > 0 && height > 0 && color_bar_props && render_mode === `density` && density_result.max_count > 0}
1075
1091
  <div
1076
1092
  bind:this={colorbar_element}
1077
1093
  class="color-bar"
1078
- style:left={`${color_bar_placement.x}px`}
1079
- style:top={`${color_bar_placement.y}px`}
1094
+ style:left={`${colorbar_tween.coords.current.x}px`}
1095
+ style:top={`${colorbar_tween.coords.current.y}px`}
1080
1096
  >
1081
1097
  <ColorBar
1082
1098
  {...color_bar_props}
@@ -1087,12 +1103,12 @@
1087
1103
  </div>
1088
1104
  {/if}
1089
1105
 
1090
- {#if annotation && annotation_placement}
1106
+ {#if width > 0 && height > 0 && annotation}
1091
1107
  <div
1092
1108
  bind:this={annotation_element}
1093
1109
  class="annotation"
1094
- style:left={`${annotation_placement.x}px`}
1095
- style:top={`${annotation_placement.y}px`}
1110
+ style:left={`${annotation_tween.coords.current.x}px`}
1111
+ style:top={`${annotation_tween.coords.current.y}px`}
1096
1112
  >
1097
1113
  {@render annotation({ height, width, fullscreen })}
1098
1114
  </div>
@@ -61,6 +61,7 @@
61
61
  has_explicit_position,
62
62
  measured_footprint,
63
63
  place_decorations,
64
+ placed_coords,
64
65
  } from '../core/auto-place'
65
66
  import {
66
67
  AXIS_DEFAULTS,
@@ -105,9 +106,9 @@
105
106
  AXIS_TITLE_OFFSET,
106
107
  calc_auto_padding,
107
108
  filter_padding,
109
+ full_footprint_or,
108
110
  LABEL_GAP_DEFAULT,
109
111
  y2_axis_label_x,
110
- measure_full_footprint,
111
112
  measure_max_tick_width,
112
113
  sample_series_obstacle_points,
113
114
  } from '../core/layout'
@@ -417,16 +418,25 @@
417
418
  const colorbar_is_horizontal = $derived(
418
419
  (color_bar?.orientation ?? `horizontal`) === `horizontal`,
419
420
  )
420
- const colorbar_footprint = $derived(
421
- colorbar_element?.offsetWidth && colorbar_element?.offsetHeight
422
- ? measure_full_footprint(colorbar_element)
423
- : colorbar_is_horizontal
424
- ? COLOR_BAR_DEFAULTS.horizontal_footprint
425
- : COLOR_BAR_DEFAULTS.vertical_footprint,
426
- )
427
- const legend_footprint = $derived(
428
- measured_footprint(legend_element, { width: 120, height: 80 }),
421
+ // Fallback estimate (with room for tick labels) used before the colorbar first
422
+ // renders; compute_element_placement measures the real footprint once it's laid out
423
+ const colorbar_fallback_size = $derived(
424
+ colorbar_is_horizontal
425
+ ? COLOR_BAR_DEFAULTS.horizontal_footprint
426
+ : COLOR_BAR_DEFAULTS.vertical_footprint,
429
427
  )
428
+ // full footprint (not the offset box): colorbar tick labels are absolutely
429
+ // positioned outside the bar and must count toward reserved margins
430
+ let colorbar_size_revision = $state(0)
431
+ let legend_size_revision = $state(0)
432
+ const colorbar_footprint = $derived.by(() => {
433
+ void colorbar_size_revision
434
+ return full_footprint_or(colorbar_element, colorbar_fallback_size)
435
+ })
436
+ const legend_footprint = $derived.by(() => {
437
+ void legend_size_revision
438
+ return measured_footprint(legend_element, { width: 120, height: 80 })
439
+ })
430
440
  const legend_has_explicit_pos = $derived(has_explicit_position(legend?.style))
431
441
 
432
442
  // Plot-specific obstacle field: series points/lines normalized to [0,1] (y=0 at top)
@@ -936,7 +946,7 @@
936
946
  let ref_lines_by_z = $derived(group_ref_lines_by_z(index_ref_lines(ref_lines)))
937
947
 
938
948
  // Calculate best legend placement using continuous grid sampling
939
- let legend_placement = $derived.by(() => {
949
+ const get_legend_placement = () => {
940
950
  const should_place =
941
951
  legend != null && (legend_data.length > 1 || Object.keys(legend ?? {}).length > 0)
942
952
 
@@ -955,23 +965,27 @@
955
965
  }
956
966
 
957
967
  return compute_element_placement(placement_config)
958
- })
968
+ }
959
969
 
960
970
  // Calculate color bar placement (coordinates with legend to avoid overlap)
961
- let color_bar_placement = $derived.by(() => {
971
+ const get_color_bar_placement = () => {
962
972
  if (!color_bar || all_color_values.length === 0 || !width || !height) return null
963
973
 
964
974
  const plot_width = width - pad.l - pad.r
965
975
  const plot_height = height - pad.t - pad.b
966
976
 
967
- // Fallback estimate (with room for tick labels) used before the colorbar first
968
- // renders; compute_element_placement measures the real footprint once it's laid out
969
- const colorbar_size = colorbar_is_horizontal
970
- ? COLOR_BAR_DEFAULTS.horizontal_footprint
971
- : COLOR_BAR_DEFAULTS.vertical_footprint
972
-
973
977
  // Build exclusion rects (avoid legend if it's placed)
974
978
  const exclude_rects: Rect[] = []
979
+ const legend_placement =
980
+ legend_is_dragging && legend_manual_position
981
+ ? legend_manual_position
982
+ : legend_has_explicit_pos && legend_element
983
+ ? { x: legend_element.offsetLeft, y: legend_element.offsetTop }
984
+ : legend_auto_outside
985
+ ? { x: legend_outside_x, y: legend_outside_y }
986
+ : legend_tween.placed()
987
+ ? legend_tween.coords.target
988
+ : get_legend_placement()
975
989
  if (legend_element && legend_placement) {
976
990
  exclude_rects.push({
977
991
  x: legend_placement.x,
@@ -984,38 +998,44 @@
984
998
  return compute_element_placement({
985
999
  plot_bounds: { x: pad.l, y: pad.t, width: plot_width, height: plot_height },
986
1000
  element: colorbar_element,
987
- element_size: colorbar_size,
1001
+ element_size: colorbar_fallback_size,
988
1002
  // Small gap from the corner; the full-footprint measurement reserves the tick
989
1003
  // labels, so this alone keeps the colorbar off the axes
990
1004
  axis_clearance: color_bar?.axis_clearance ?? 15,
991
1005
  exclude_rects,
992
1006
  points: plot_points_for_placement,
993
1007
  })
994
- })
1008
+ }
995
1009
 
996
1010
  // Active legend placement (null if user set explicit position in style)
997
- let active_legend_placement = $derived(
998
- legend_placement && !legend_has_explicit_pos ? legend_placement : null,
999
- )
1011
+ const get_active_legend_placement = () =>
1012
+ legend_has_explicit_pos ? null : get_legend_placement()
1000
1013
 
1001
1014
  // Tweened colorbar/legend coordinates with shared placement stability gating
1002
- const colorbar_tween = create_placed_tween({
1003
- placement: () => color_bar_placement,
1004
- dims: () => ({ width, height }),
1005
- responsive: () => color_bar?.responsive ?? false,
1006
- element: () => colorbar_element,
1007
- tween: () => color_bar?.tween,
1008
- })
1009
1015
  const legend_tween = create_placed_tween({
1010
- placement: () => active_legend_placement,
1016
+ placement: get_active_legend_placement,
1011
1017
  dims: () => ({ width, height }),
1012
1018
  responsive: () => legend?.responsive ?? false,
1013
1019
  element: () => legend_element,
1014
1020
  tween: () => legend?.tween,
1021
+ on_element_resize: () => (legend_size_revision += 1),
1022
+ placement_revision: () => colorbar_size_revision,
1015
1023
  // Leave coords alone mid-drag; once dragged, the manual position wins permanently
1016
1024
  suspended: () => legend_is_dragging,
1017
1025
  manual_position: () => legend_manual_position,
1018
1026
  })
1027
+ const colorbar_tween = create_placed_tween({
1028
+ placement: get_color_bar_placement,
1029
+ dims: () => ({ width, height }),
1030
+ responsive: () => color_bar?.responsive ?? false,
1031
+ element: () => colorbar_element,
1032
+ tween: () => color_bar?.tween,
1033
+ on_element_resize: () => (colorbar_size_revision += 1),
1034
+ placement_revision: () =>
1035
+ `${legend_size_revision}:${legend_tween.coords.target.x}:${legend_tween.coords.target.y}:${
1036
+ legend_auto_outside ? `${legend_outside_x}:${legend_outside_y}` : `inside`
1037
+ }:${legend?.style ?? ``}`,
1038
+ })
1019
1039
 
1020
1040
  // Generate axis ticks - consolidated into single derived for efficiency
1021
1041
  let axis_ticks = $derived.by(() => {
@@ -1091,7 +1111,9 @@
1091
1111
  ({ x: final_x_axis, x2: final_x2_axis, y: final_y_axis, y2: final_y2_axis })[axis]
1092
1112
  .scale_type,
1093
1113
  // Clamp to at least 1 to avoid Infinity deltas when padding equals container size
1094
- plot_dims: () => ({
1114
+ plot_bounds: () => ({
1115
+ x: pad.l,
1116
+ y: pad.t,
1095
1117
  width: Math.max(1, width - pad.l - pad.r),
1096
1118
  height: Math.max(1, height - pad.t - pad.b),
1097
1119
  }),
@@ -1928,7 +1950,7 @@
1928
1950
  {/if}
1929
1951
 
1930
1952
  <!-- Color Bar -->
1931
- {#if color_bar && all_color_values.length > 0 && color_bar_placement}
1953
+ {#if width > 0 && height > 0 && color_bar && all_color_values.length > 0}
1932
1954
  {@const color_domain = [
1933
1955
  (typeof color_scale === `string` ? undefined : color_scale.value_range)?.[0] ??
1934
1956
  auto_color_range[0],
@@ -1968,22 +1990,15 @@
1968
1990
  {#if legend != null && legend_data.length > 0 && (legend_data.length > 1 || Object.keys(legend ?? {}).length > 0)}
1969
1991
  {@const default_x = pad.l + 10}
1970
1992
  {@const default_y = pad.t + 10}
1971
- {@const current_x =
1972
- legend_is_dragging && legend_manual_position
1973
- ? legend_manual_position.x
1974
- : legend_auto_outside
1975
- ? legend_outside_x
1976
- : legend_placement
1977
- ? legend_tween.coords.current.x
1978
- : default_x}
1979
- {@const current_y =
1980
- legend_is_dragging && legend_manual_position
1981
- ? legend_manual_position.y
1982
- : legend_auto_outside
1983
- ? legend_outside_y
1984
- : legend_placement
1985
- ? legend_tween.coords.current.y
1986
- : default_y}
1993
+ {@const auto_position = placed_coords(
1994
+ legend_auto_outside,
1995
+ { x: legend_outside_x, y: legend_outside_y },
1996
+ legend_tween.placed(),
1997
+ legend_tween.coords.current,
1998
+ { x: default_x, y: default_y },
1999
+ )}
2000
+ {@const current_position =
2001
+ legend_is_dragging && legend_manual_position ? legend_manual_position : auto_position}
1987
2002
  <PlotLegend
1988
2003
  bind:root_element={legend_element}
1989
2004
  series_data={legend_data}
@@ -2046,8 +2061,8 @@
2046
2061
  }}
2047
2062
  style={`
2048
2063
  position: absolute;
2049
- left: ${current_x}px;
2050
- top: ${current_y}px;
2064
+ left: ${current_position.x}px;
2065
+ top: ${current_position.y}px;
2051
2066
  pointer-events: auto;
2052
2067
  ${legend?.style ?? ``}
2053
2068
  `}
@@ -67,7 +67,7 @@ export type BinTransforms = {
67
67
  export declare function scale_bin_transform(scale_type?: ScaleType): BinTransform;
68
68
  export declare const get_metadata_at: <Metadata>(metadata: DensePointSeries<Metadata>[`metadata`], point_idx: number) => Metadata | undefined;
69
69
  export declare const range_bounds: (range: Vec2) => Vec2;
70
- export declare function series_extents(series: readonly DensePointSeries[]): {
70
+ export declare function series_extents(series: readonly DensePointSeries[], x_scale_type?: ScaleType, y_scale_type?: ScaleType): {
71
71
  x: Vec2;
72
72
  y: Vec2;
73
73
  };
@@ -33,39 +33,55 @@ export const range_bounds = (range) => range[0] <= range[1] ? range : [range[1],
33
33
  const in_bounds = (value, min, max) => Number.isFinite(value) && value >= min && value <= max;
34
34
  const value_bin = (value, min, span, bins) => Math.min(bins - 1, Math.max(0, Math.floor(((value - min) / span) * bins)));
35
35
  const series_length = (srs) => Math.min(srs.x.length, srs.y.length);
36
- const padded_extent = (min, max) => {
36
+ const padded_extent = (min, max, scale_type) => {
37
+ const log_scale = get_scale_type_name(scale_type) === `log`;
37
38
  if (!Number.isFinite(min) || !Number.isFinite(max))
38
- return [0, 1];
39
- if (min === max)
40
- return [min - 0.5, max + 0.5];
41
- const padding = (max - min) * 0.05;
42
- return [min - padding, max + padding];
39
+ return log_scale ? [1, 10] : [0, 1];
40
+ const { forward, inverse } = scale_bin_transform(scale_type);
41
+ const t_min = forward(min);
42
+ const t_max = forward(max);
43
+ if (t_min === t_max) {
44
+ if (log_scale) {
45
+ const center = Math.max(min, LOG_EPS);
46
+ return [Math.max(LOG_EPS, center / Math.sqrt(10)), center * Math.sqrt(10)];
47
+ }
48
+ return [inverse(t_min - 0.5), inverse(t_max + 0.5)];
49
+ }
50
+ const padding = (t_max - t_min) * 0.05;
51
+ const clamp = (val) => Math.min(Number.MAX_VALUE, Math.max(-Number.MAX_VALUE, val));
52
+ return [clamp(inverse(t_min - padding)), clamp(inverse(t_max + padding))];
43
53
  };
44
- export function series_extents(series) {
54
+ export function series_extents(series, x_scale_type, y_scale_type) {
45
55
  let x_min = Infinity;
46
56
  let x_max = -Infinity;
47
57
  let y_min = Infinity;
48
58
  let y_max = -Infinity;
59
+ const log_x = get_scale_type_name(x_scale_type) === `log`;
60
+ const log_y = get_scale_type_name(y_scale_type) === `log`;
49
61
  for (const srs of series) {
50
62
  const n_points = series_length(srs);
51
63
  for (let idx = 0; idx < n_points; idx++) {
52
64
  const x = srs.x[idx];
53
65
  const y = srs.y[idx];
54
- if (Number.isFinite(x)) {
55
- if (x < x_min)
56
- x_min = x;
57
- if (x > x_max)
58
- x_max = x;
59
- }
60
- if (Number.isFinite(y)) {
61
- if (y < y_min)
62
- y_min = y;
63
- if (y > y_max)
64
- y_max = y;
65
- }
66
+ if (!Number.isFinite(x) || !Number.isFinite(y))
67
+ continue;
68
+ // Align with bin_points / log scale floor so sub-LOG_EPS samples don't widen extent
69
+ if ((log_x && x < LOG_EPS) || (log_y && y < LOG_EPS))
70
+ continue;
71
+ if (x < x_min)
72
+ x_min = x;
73
+ if (x > x_max)
74
+ x_max = x;
75
+ if (y < y_min)
76
+ y_min = y;
77
+ if (y > y_max)
78
+ y_max = y;
66
79
  }
67
80
  }
68
- return { x: padded_extent(x_min, x_max), y: padded_extent(y_min, y_max) };
81
+ return {
82
+ x: padded_extent(x_min, x_max, x_scale_type),
83
+ y: padded_extent(y_min, y_max, y_scale_type),
84
+ };
69
85
  }
70
86
  export function bin_points(series, x_range, y_range, x_bins, y_bins, transforms) {
71
87
  const counts = new Uint32Array(x_bins * y_bins);
@@ -32,6 +32,7 @@
32
32
  hierarchy_legend_items,
33
33
  is_activation_key,
34
34
  node_handler_props,
35
+ observe_height,
35
36
  pointer_pos,
36
37
  prune_muted_ids,
37
38
  safe_hierarchy_layout,
@@ -60,6 +61,7 @@
60
61
  import { Tween, type TweenOptions } from 'svelte/motion'
61
62
  import { SvelteSet } from 'svelte/reactivity'
62
63
 
64
+ // Preserve the established outer inset; pass `padding` to override chart-edge space.
63
65
  const DEFAULT_PADDING: Required<Sides> = { t: 10, b: 10, l: 10, r: 10 }
64
66
 
65
67
  // An arc with its current screen-space geometry (angles in radians, radii in px)
@@ -184,10 +186,10 @@
184
186
  let pad = $derived(filter_padding(padding, DEFAULT_PADDING))
185
187
  let inner_width = $derived(Math.max(0, width - pad.l - pad.r))
186
188
  let avail_height = $derived(Math.max(0, height - pad.t - pad.b))
187
- // measured height of the bottom colorbar, reserved from the chart so it never overlaps
188
- // the arcs (16px covers its bottom offset + a small gap). reset to 0 when the colorbar
189
- // is hidden (effect below) since bind:clientHeight doesn't clear on unmount; capped at
190
- // half the area so a bad measurement can't collapse the chart
189
+ // measured height of the bottom colorbar (via observe_height, which resets it
190
+ // to 0 on unmount), reserved from the chart so it never overlaps the arcs
191
+ // (16px covers its bottom offset + a small gap); capped at half the area so a
192
+ // bad measurement can't collapse the chart
191
193
  let colorbar_height = $state(0)
192
194
  let colorbar_reserve = $derived(
193
195
  colorbar_height > 0 ? Math.min(colorbar_height + 16, avail_height / 2) : 0,
@@ -310,11 +312,6 @@
310
312
  )
311
313
  const arc_color = (arc: PositionedArc<Metadata>): string =>
312
314
  metric?.colors[arc.node_idx] ?? arc.color
313
- // release the colorbar's reserved chart space when it's not rendered
314
- $effect(() => {
315
- if (!metric || colorbar == null) colorbar_height = 0
316
- })
317
-
318
315
  // Hovered arc + its ancestors/descendants stay fully opaque, others dim
319
316
  let arc_dim = $derived(compute_node_dim(layout.arcs, muted_ids, hovered_idx))
320
317
 
@@ -789,17 +786,19 @@
789
786
  {/if}
790
787
 
791
788
  {#if metric && colorbar != null}
792
- <div
793
- bind:clientHeight={colorbar_height}
794
- style="position: absolute; bottom: var(--sunburst-colorbar-bottom, 8px); left: var(--sunburst-colorbar-left, 50%); transform: var(--sunburst-colorbar-transform, translateX(-50%)); width: var(--sunburst-colorbar-width, 40%); min-width: 120px; pointer-events: auto;"
795
- >
796
- <ColorBar
797
- {color_scale}
798
- range={metric.range}
799
- {...colorbar}
800
- wrapper_style={`width: 100%; ${colorbar?.wrapper_style ?? ``}`}
801
- />
802
- </div>
789
+ <!-- positioning + height measurement live on ColorBar's own root (via rest
790
+ props + attachment) instead of an extra wrapper div -->
791
+ <ColorBar
792
+ {color_scale}
793
+ range={metric.range}
794
+ {...colorbar}
795
+ wrapper_style="{colorbar?.orientation === `vertical`
796
+ ? `--cbar-height: var(--sunburst-colorbar-height, 150px);`
797
+ : ``} {colorbar?.wrapper_style ?? ``}"
798
+ style="position: absolute; bottom: var(--sunburst-colorbar-bottom, 8px); left: var(--sunburst-colorbar-left, 50%); transform: var(--sunburst-colorbar-transform, translateX(-50%)); width: var(--sunburst-colorbar-width, 40%); min-width: 120px; pointer-events: auto; {colorbar?.style ??
799
+ ``}"
800
+ {@attach observe_height((px) => (colorbar_height = px))}
801
+ />
803
802
  {/if}
804
803
 
805
804
  {@render children?.({ height, width, fullscreen })}
@@ -818,6 +817,7 @@
818
817
  flex: var(--sunburst-flex, 1 1 auto);
819
818
  display: var(--sunburst-display, flex);
820
819
  flex-direction: column;
820
+ /* Use the plot background by default; set --sunburst-bg to override it. */
821
821
  background: var(--sunburst-bg, var(--plot-bg));
822
822
  border-radius: var(--sunburst-border-radius, var(--border-radius, 3pt));
823
823
  }