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.
- package/dist/FilePicker.svelte +40 -50
- package/dist/api/optimade.js +10 -7
- package/dist/app.css +15 -0
- package/dist/brillouin/BrillouinZone.svelte +18 -9
- package/dist/brillouin/BrillouinZoneScene.svelte +20 -29
- package/dist/brillouin/compute.js +10 -16
- package/dist/brillouin/geometry.js +2 -2
- package/dist/chempot-diagram/ChemPotDiagram.svelte +18 -15
- package/dist/chempot-diagram/ChemPotDiagram2D.svelte +17 -113
- package/dist/chempot-diagram/ChemPotDiagram3D.svelte +104 -264
- package/dist/chempot-diagram/async-compute.svelte.js +12 -13
- package/dist/chempot-diagram/color.d.ts +21 -1
- package/dist/chempot-diagram/color.js +70 -1
- package/dist/chempot-diagram/compute.d.ts +7 -0
- package/dist/chempot-diagram/compute.js +85 -26
- package/dist/chempot-diagram/export.js +1 -6
- package/dist/chempot-diagram/temperature.js +9 -10
- package/dist/composition/BarChart.svelte +7 -9
- package/dist/composition/Composition.svelte +3 -8
- package/dist/composition/Formula.svelte +2 -2
- package/dist/composition/FormulaFilter.svelte +27 -93
- package/dist/composition/format.js +6 -8
- package/dist/composition/parse.js +50 -105
- package/dist/constants.d.ts +0 -1
- package/dist/constants.js +6 -17
- package/dist/convex-hull/ConvexHull2D.svelte +0 -2
- package/dist/convex-hull/ConvexHull3D.svelte +19 -47
- package/dist/convex-hull/ConvexHull4D.svelte +65 -95
- package/dist/convex-hull/StructurePopup.svelte +1 -1
- package/dist/convex-hull/gas-thermodynamics.js +21 -53
- package/dist/convex-hull/helpers.js +1 -7
- package/dist/convex-hull/thermodynamics.js +19 -27
- package/dist/coordination/CoordinationBarPlot.svelte +12 -21
- package/dist/element/BohrAtom.svelte +0 -1
- package/dist/element/ElementTile.svelte +26 -44
- package/dist/element/data.d.ts +2 -0
- package/dist/element/data.js +5 -1
- package/dist/element/index.d.ts +1 -1
- package/dist/element/index.js +1 -1
- package/dist/fermi-surface/FermiSurface.svelte +22 -23
- package/dist/fermi-surface/FermiSurface.svelte.d.ts +2 -6
- package/dist/fermi-surface/FermiSurfaceScene.svelte +32 -77
- package/dist/fermi-surface/compute.js +22 -55
- package/dist/fermi-surface/parse.js +38 -70
- package/dist/file-viewer/JsonBrowser.svelte +1043 -0
- package/dist/file-viewer/JsonBrowser.svelte.d.ts +9 -0
- package/dist/file-viewer/PlotPanel.svelte +350 -0
- package/dist/file-viewer/PlotPanel.svelte.d.ts +9 -0
- package/dist/file-viewer/TrajectoryWithDos.svelte +30 -0
- package/dist/file-viewer/TrajectoryWithDos.svelte.d.ts +10 -0
- package/dist/file-viewer/detect.d.ts +11 -0
- package/dist/file-viewer/detect.js +381 -0
- package/dist/file-viewer/eligibility.d.ts +4 -0
- package/dist/file-viewer/eligibility.js +53 -0
- package/dist/file-viewer/host-protocol.d.ts +50 -0
- package/dist/file-viewer/host-transfer.d.ts +25 -0
- package/dist/file-viewer/host-transfer.js +42 -0
- package/dist/file-viewer/index.d.ts +5 -0
- package/dist/file-viewer/index.js +5 -0
- package/dist/file-viewer/main.d.ts +44 -0
- package/dist/file-viewer/main.js +535 -0
- package/dist/file-viewer/parse.d.ts +9 -0
- package/dist/file-viewer/parse.js +170 -0
- package/dist/file-viewer/plot-utils.d.ts +24 -0
- package/dist/file-viewer/plot-utils.js +191 -0
- package/dist/file-viewer/types.d.ts +4 -0
- package/dist/file-viewer/types.js +5 -0
- package/dist/heatmap-matrix/HeatmapMatrix.svelte +57 -84
- package/dist/heatmap-matrix/HeatmapMatrixControls.svelte.d.ts +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/io/ExportPane.svelte +1 -1
- package/dist/io/decompress.d.ts +2 -0
- package/dist/io/decompress.js +3 -2
- package/dist/io/export.d.ts +7 -4
- package/dist/io/export.js +70 -18
- package/dist/io/file-drop.js +60 -35
- package/dist/io/url-drop.d.ts +2 -0
- package/dist/io/url-drop.js +28 -16
- package/dist/isosurface/Isosurface.svelte +687 -196
- package/dist/isosurface/Isosurface.svelte.d.ts +7 -1
- package/dist/isosurface/IsosurfaceControls.svelte +564 -85
- package/dist/isosurface/IsosurfaceControls.svelte.d.ts +1 -1
- package/dist/isosurface/VolumeSlice.svelte +224 -0
- package/dist/isosurface/VolumeSlice.svelte.d.ts +28 -0
- package/dist/isosurface/coloring.d.ts +20 -0
- package/dist/isosurface/coloring.js +118 -0
- package/dist/isosurface/geometry-worker-types.d.ts +38 -0
- package/dist/isosurface/geometry.worker.d.ts +1 -0
- package/dist/isosurface/geometry.worker.js +59 -0
- package/dist/isosurface/grid.d.ts +10 -0
- package/dist/isosurface/grid.js +31 -0
- package/dist/isosurface/index.d.ts +4 -0
- package/dist/isosurface/index.js +4 -0
- package/dist/isosurface/profile.d.ts +12 -0
- package/dist/isosurface/profile.js +19 -0
- package/dist/isosurface/sampling.d.ts +34 -0
- package/dist/isosurface/sampling.js +408 -0
- package/dist/isosurface/slice-rendering.d.ts +13 -0
- package/dist/isosurface/slice-rendering.js +79 -0
- package/dist/isosurface/slice.d.ts +22 -2
- package/dist/isosurface/slice.js +200 -116
- package/dist/isosurface/types.d.ts +24 -1
- package/dist/isosurface/types.js +120 -8
- package/dist/json-path.d.ts +4 -0
- package/dist/json-path.js +89 -0
- package/dist/labels.js +1 -1
- package/dist/layout/InfoCard.svelte +1 -3
- package/dist/layout/SettingsSection.svelte +52 -61
- package/dist/layout/SettingsSection.svelte.d.ts +6 -7
- package/dist/layout/ViewerChrome.svelte +7 -1
- package/dist/layout/json-tree/JsonNode.svelte +14 -48
- package/dist/layout/json-tree/JsonTree.svelte +48 -95
- package/dist/layout/json-tree/JsonValue.svelte +3 -18
- package/dist/layout/json-tree/utils.d.ts +0 -3
- package/dist/layout/json-tree/utils.js +50 -179
- package/dist/marching-cubes.d.ts +8 -1
- package/dist/marching-cubes.js +115 -48
- package/dist/math.d.ts +3 -2
- package/dist/math.js +29 -38
- package/dist/overlays/ContextMenu.svelte +15 -32
- package/dist/overlays/DraggablePane.svelte +39 -58
- package/dist/overlays/DraggablePane.svelte.d.ts +1 -1
- package/dist/overlays/GlassChip.svelte +3 -1
- package/dist/periodic-table/PeriodicTableControls.svelte +26 -52
- package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte +18 -34
- package/dist/phase-diagram/build-diagram.js +1 -6
- package/dist/phase-diagram/svg-to-diagram.js +9 -14
- package/dist/plot/bar/BarPlot.svelte +38 -42
- package/dist/plot/bar/SpacegroupBarPlot.svelte +18 -42
- package/dist/plot/box/BoxPlot.svelte +27 -23
- package/dist/plot/core/auto-place.d.ts +1 -1
- package/dist/plot/core/auto-place.js +1 -1
- package/dist/plot/core/data-transform.d.ts +1 -2
- package/dist/plot/core/data-transform.js +0 -10
- package/dist/plot/core/layout.d.ts +12 -5
- package/dist/plot/core/layout.js +37 -27
- package/dist/plot/core/pan-zoom.svelte.d.ts +2 -4
- package/dist/plot/core/pan-zoom.svelte.js +34 -17
- package/dist/plot/core/placed-tween.svelte.d.ts +3 -0
- package/dist/plot/core/placed-tween.svelte.js +68 -21
- package/dist/plot/core/svg.js +0 -1
- package/dist/plot/core/utils/hierarchy-chart.d.ts +1 -0
- package/dist/plot/core/utils/hierarchy-chart.js +12 -0
- package/dist/plot/core/utils/hierarchy-labels.js +3 -2
- package/dist/plot/histogram/Histogram.svelte +21 -26
- package/dist/plot/sankey/sankey.js +13 -9
- package/dist/plot/scatter/BinnedScatterPlot.svelte +59 -43
- package/dist/plot/scatter/ScatterPlot.svelte +67 -52
- package/dist/plot/scatter/adaptive-density.d.ts +1 -1
- package/dist/plot/scatter/adaptive-density.js +36 -20
- package/dist/plot/sunburst/Sunburst.svelte +20 -20
- package/dist/plot/treemap/Treemap.svelte +175 -60
- package/dist/plot/treemap/Treemap.svelte.d.ts +6 -0
- package/dist/plot/treemap/index.d.ts +1 -0
- package/dist/plot/treemap/labels.d.ts +38 -0
- package/dist/plot/treemap/labels.js +80 -0
- package/dist/plot/treemap/treemap.d.ts +4 -2
- package/dist/plot/treemap/treemap.js +5 -2
- package/dist/rdf/RdfPlot.svelte +17 -35
- package/dist/rdf/calc-rdf.js +59 -44
- package/dist/rdf/index.d.ts +0 -1
- package/dist/sanitize.js +84 -26
- package/dist/scene/SceneCamera.svelte +1 -1
- package/dist/scene/SceneCamera.svelte.d.ts +1 -1
- package/dist/scene/index.d.ts +1 -2
- package/dist/scene/index.js +1 -1
- package/dist/scene/{props.js → props.svelte.js} +15 -1
- package/dist/settings.js +159 -482
- package/dist/spectral/Bands.svelte +151 -158
- package/dist/spectral/BrillouinBandsDos.svelte +4 -19
- package/dist/spectral/Dos.svelte +26 -24
- package/dist/spectral/helpers.js +40 -105
- package/dist/structure/AtomLegend.svelte +8 -13
- package/dist/structure/AtomLegend.svelte.d.ts +2 -3
- package/dist/structure/CellSelect.svelte +35 -10
- package/dist/structure/Structure.svelte +177 -99
- package/dist/structure/Structure.svelte.d.ts +1 -1
- package/dist/structure/StructureCarousel.svelte +10 -3
- package/dist/structure/StructureControls.svelte +8 -26
- package/dist/structure/StructureControls.svelte.d.ts +1 -1
- package/dist/structure/StructureInfoPane.svelte +65 -76
- package/dist/structure/StructureScene.svelte +75 -109
- package/dist/structure/StructureScene.svelte.d.ts +3 -1
- package/dist/structure/StructureViewport.svelte +120 -55
- package/dist/structure/StructureViewport.svelte.d.ts +3 -1
- package/dist/structure/atom-properties.d.ts +1 -1
- package/dist/structure/atom-properties.js +4 -3
- package/dist/structure/bond-order-perception.js +3 -8
- package/dist/structure/bonding.d.ts +0 -1
- package/dist/structure/bonding.js +16 -23
- package/dist/structure/export.js +50 -74
- package/dist/structure/format-detect.js +5 -7
- package/dist/structure/index.js +6 -12
- package/dist/structure/parse.js +38 -100
- package/dist/structure/partial-occupancy.js +7 -4
- package/dist/structure/pbc.js +8 -10
- package/dist/structure/polyhedra.js +5 -4
- package/dist/structure/prop-groups.d.ts +85 -0
- package/dist/structure/prop-groups.js +18 -0
- package/dist/symmetry/SymmetryStats.svelte +0 -4
- package/dist/symmetry/WyckoffTable.svelte +5 -4
- package/dist/symmetry/cell-transform.js +5 -14
- package/dist/symmetry/index.js +6 -9
- package/dist/table/HeatmapTable.svelte +61 -103
- package/dist/table/ToggleMenu.svelte +166 -44
- package/dist/table/ToggleMenu.svelte.d.ts +1 -1
- package/dist/theme/embedded.d.ts +18 -0
- package/dist/theme/embedded.js +206 -0
- package/dist/theme/index.d.ts +4 -0
- package/dist/theme/index.js +1 -2
- package/dist/theme/themes.mjs +31 -60
- package/dist/tooltip/TooltipContent.svelte +1 -1
- package/dist/tooltip/TooltipContent.svelte.d.ts +1 -1
- package/dist/tooltip/index.d.ts +8 -1
- package/dist/trajectory/Trajectory.svelte +140 -120
- package/dist/trajectory/TrajectoryError.svelte +1 -66
- package/dist/trajectory/TrajectoryExportPane.svelte +0 -1
- package/dist/trajectory/TrajectoryInfoPane.svelte +55 -80
- package/dist/trajectory/TrajectoryInfoPane.svelte.d.ts +2 -1
- package/dist/trajectory/extract.js +19 -29
- package/dist/trajectory/format-detect.d.ts +2 -0
- package/dist/trajectory/format-detect.js +7 -5
- package/dist/trajectory/frame-reader.js +3 -4
- package/dist/trajectory/helpers.js +16 -49
- package/dist/trajectory/index.js +2 -8
- package/dist/trajectory/parse/ase.js +1 -1
- package/dist/trajectory/parse/index.d.ts +4 -3
- package/dist/trajectory/parse/index.js +12 -17
- package/dist/trajectory/parse/vaspout-h5.d.ts +0 -1
- package/dist/trajectory/parse/vaspout-h5.js +1 -4
- package/dist/trajectory/parse/xyz.js +48 -2
- package/dist/trajectory/plotting.js +14 -37
- package/dist/xrd/XrdPlot.svelte +43 -68
- package/dist/xrd/calc-xrd.js +22 -58
- package/dist/xrd/parse.js +13 -22
- package/package.json +40 -8
- package/readme.md +6 -5
- package/dist/scene/types.d.ts +0 -26
- package/dist/tooltip/types.d.ts +0 -8
- package/dist/trajectory/constants.d.ts +0 -6
- package/dist/trajectory/constants.js +0 -11
- /package/dist/{scene/types.js → file-viewer/host-protocol.js} +0 -0
- /package/dist/{tooltip/types.js → isosurface/geometry-worker-types.js} +0 -0
package/dist/plot/core/layout.js
CHANGED
|
@@ -31,23 +31,35 @@ export function measure_text_width(text, font = `12px sans-serif`) {
|
|
|
31
31
|
ctx.font = font;
|
|
32
32
|
return ctx.measureText(text).width;
|
|
33
33
|
}
|
|
34
|
-
|
|
35
|
-
// offset box. Colorbar tick labels are position:absolute outside the bar, so
|
|
36
|
-
// offsetWidth/offsetHeight underestimate the space they actually occupy — which lets
|
|
37
|
-
// auto-placement put the colorbar where its labels overlap the axes.
|
|
38
|
-
export function measure_full_footprint(el) {
|
|
34
|
+
function measure_full_footprint(el) {
|
|
39
35
|
const root = el.getBoundingClientRect();
|
|
36
|
+
let left = root.left;
|
|
37
|
+
let top = root.top;
|
|
40
38
|
let right = root.right;
|
|
41
39
|
let bottom = root.bottom;
|
|
42
40
|
for (const child of el.querySelectorAll(`*`)) {
|
|
43
41
|
const rect = child.getBoundingClientRect();
|
|
44
42
|
if (rect.width === 0 && rect.height === 0)
|
|
45
43
|
continue;
|
|
44
|
+
left = Math.min(left, rect.left);
|
|
45
|
+
top = Math.min(top, rect.top);
|
|
46
46
|
right = Math.max(right, rect.right);
|
|
47
47
|
bottom = Math.max(bottom, rect.bottom);
|
|
48
48
|
}
|
|
49
|
-
return {
|
|
49
|
+
return {
|
|
50
|
+
width: right - left,
|
|
51
|
+
height: bottom - top,
|
|
52
|
+
offset_x: left - root.left,
|
|
53
|
+
offset_y: top - root.top,
|
|
54
|
+
};
|
|
50
55
|
}
|
|
56
|
+
// Full footprint once the element is laid out, else `fallback` (offset dims read 0
|
|
57
|
+
// before first render). NOT interchangeable with auto-place's measured_footprint:
|
|
58
|
+
// that returns the offset box, which underestimates elements with overflowing
|
|
59
|
+
// absolutely-positioned descendants like colorbar tick labels.
|
|
60
|
+
export const full_footprint_or = (el, fallback) => el?.offsetWidth && el?.offsetHeight
|
|
61
|
+
? measure_full_footprint(el)
|
|
62
|
+
: { ...fallback, offset_x: 0, offset_y: 0 };
|
|
51
63
|
// Measure the widest formatted tick label. Used for auto-padding and label placement.
|
|
52
64
|
export const measure_max_tick_width = (ticks, format = ``) => ticks.length === 0
|
|
53
65
|
? 0
|
|
@@ -148,7 +160,7 @@ const DISTANCE_WEIGHT = 0.001;
|
|
|
148
160
|
const CORNER_WEIGHT = 5.0;
|
|
149
161
|
const MAX_SAMPLE_POINTS = 500;
|
|
150
162
|
// Check if a point is inside a rectangle
|
|
151
|
-
const point_in_rect = (point, rect) => point.x >= rect.x &&
|
|
163
|
+
export const point_in_rect = (point, rect) => point.x >= rect.x &&
|
|
152
164
|
point.x <= rect.x + rect.width &&
|
|
153
165
|
point.y >= rect.y &&
|
|
154
166
|
point.y <= rect.y + rect.height;
|
|
@@ -195,14 +207,16 @@ export function compute_element_placement(config) {
|
|
|
195
207
|
// Measure the element's full footprint (incl. descendants that overflow its box,
|
|
196
208
|
// such as colorbar tick labels) once it's laid out; fall back to element_size before
|
|
197
209
|
// first render. Centralizing this keeps every plot's auto-placement overlap-free.
|
|
198
|
-
const { width: elem_width, height: elem_height } = element
|
|
199
|
-
? measure_full_footprint(element)
|
|
200
|
-
: element_size;
|
|
210
|
+
const { width: elem_width, height: elem_height, offset_x, offset_y, } = full_footprint_or(element, element_size);
|
|
201
211
|
// Calculate valid placement region (plot bounds minus axis clearance)
|
|
202
|
-
const
|
|
203
|
-
const
|
|
204
|
-
const
|
|
205
|
-
const
|
|
212
|
+
const plot_left = plot_bounds.x + axis_clearance;
|
|
213
|
+
const plot_right = plot_bounds.x + plot_bounds.width - axis_clearance;
|
|
214
|
+
const plot_top = plot_bounds.y + axis_clearance;
|
|
215
|
+
const plot_bottom = plot_bounds.y + plot_bounds.height - axis_clearance;
|
|
216
|
+
const valid_x_min = plot_left - offset_x;
|
|
217
|
+
const valid_y_min = plot_top - offset_y;
|
|
218
|
+
const valid_x_max = plot_right - offset_x - elem_width;
|
|
219
|
+
const valid_y_max = plot_bottom - offset_y - elem_height;
|
|
206
220
|
// Handle case where element is too large for the valid region
|
|
207
221
|
const effective_x_min = Math.min(valid_x_min, valid_x_max);
|
|
208
222
|
const effective_x_max = Math.max(valid_x_min, valid_x_max);
|
|
@@ -225,18 +239,14 @@ export function compute_element_placement(config) {
|
|
|
225
239
|
? (effective_y_max - effective_y_min) / (grid_resolution - 1)
|
|
226
240
|
: 0;
|
|
227
241
|
// Precompute plot corners (constant across all candidates)
|
|
228
|
-
const plot_left = valid_x_min;
|
|
229
|
-
const plot_right = plot_bounds.x + plot_bounds.width - axis_clearance;
|
|
230
|
-
const plot_top = valid_y_min;
|
|
231
|
-
const plot_bottom = plot_bounds.y + plot_bounds.height - axis_clearance;
|
|
232
242
|
const max_corner_dist = euclidean_dist([plot_left, plot_top], [plot_right, plot_bottom]);
|
|
233
243
|
for (let grid_x = 0; grid_x < grid_resolution; grid_x++) {
|
|
234
244
|
for (let grid_y = 0; grid_y < grid_resolution; grid_y++) {
|
|
235
245
|
const cand_x = effective_x_min + grid_x * x_step;
|
|
236
246
|
const cand_y = effective_y_min + grid_y * y_step;
|
|
237
247
|
const cand_rect = {
|
|
238
|
-
x: cand_x,
|
|
239
|
-
y: cand_y,
|
|
248
|
+
x: cand_x + offset_x,
|
|
249
|
+
y: cand_y + offset_y,
|
|
240
250
|
width: elem_width,
|
|
241
251
|
height: elem_height,
|
|
242
252
|
};
|
|
@@ -250,8 +260,8 @@ export function compute_element_placement(config) {
|
|
|
250
260
|
// Count points overlapping this candidate position
|
|
251
261
|
let overlap_count = 0;
|
|
252
262
|
let min_distance_sq = Infinity;
|
|
253
|
-
const center_x =
|
|
254
|
-
const center_y =
|
|
263
|
+
const center_x = cand_rect.x + elem_width / 2;
|
|
264
|
+
const center_y = cand_rect.y + elem_height / 2;
|
|
255
265
|
for (const point of sampled_points) {
|
|
256
266
|
if (point_in_rect(point, cand_rect)) {
|
|
257
267
|
overlap_count++;
|
|
@@ -271,12 +281,12 @@ export function compute_element_placement(config) {
|
|
|
271
281
|
// Corner preference: use element's actual corner (not center) for distance
|
|
272
282
|
// This ensures a wide element at the left edge gets proper corner credit
|
|
273
283
|
// (measured footprint, same as cand_rect — not the element_size fallback)
|
|
274
|
-
const elem_right =
|
|
275
|
-
const elem_bottom =
|
|
284
|
+
const elem_right = cand_rect.x + elem_width;
|
|
285
|
+
const elem_bottom = cand_rect.y + elem_height;
|
|
276
286
|
// Distance from element's matching corner to each plot corner
|
|
277
|
-
const min_corner_dist = Math.min(euclidean_dist([
|
|
278
|
-
euclidean_dist([elem_right,
|
|
279
|
-
euclidean_dist([
|
|
287
|
+
const min_corner_dist = Math.min(euclidean_dist([cand_rect.x, cand_rect.y], [plot_left, plot_top]), // top-left
|
|
288
|
+
euclidean_dist([elem_right, cand_rect.y], [plot_right, plot_top]), // top-right
|
|
289
|
+
euclidean_dist([cand_rect.x, elem_bottom], [plot_left, plot_bottom]), // bottom-left
|
|
280
290
|
euclidean_dist([elem_right, elem_bottom], [plot_right, plot_bottom]));
|
|
281
291
|
// Higher bonus for positions closer to corners (0 = at corner, 1 = far from all)
|
|
282
292
|
const corner_bonus = max_corner_dist > 0 ? (1 - min_corner_dist / max_corner_dist) * CORNER_WEIGHT : 0;
|
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
import type { Point2D, Vec2 } from '../../math';
|
|
2
|
+
import { type Rect } from './layout';
|
|
2
3
|
import type { AxisRanges, PanConfig, ScaleType } from './types';
|
|
3
4
|
type Axis = `x` | `x2` | `y` | `y2`;
|
|
4
5
|
export interface PanZoomOptions {
|
|
5
6
|
ranges: () => AxisRanges;
|
|
6
7
|
scale_type: (axis: Axis) => ScaleType | undefined;
|
|
7
|
-
|
|
8
|
-
width: number;
|
|
9
|
-
height: number;
|
|
10
|
-
};
|
|
8
|
+
plot_bounds: () => Rect;
|
|
11
9
|
pan: () => PanConfig | undefined;
|
|
12
10
|
set_range: (axis: Axis, range: Vec2) => void;
|
|
13
11
|
svg: () => SVGElement | null;
|
|
@@ -3,17 +3,18 @@
|
|
|
3
3
|
// per-component policy (which axis props to write on rect-zoom, reset semantics)
|
|
4
4
|
// stays in the component via the on_rect_zoom/on_reset/set_range callbacks.
|
|
5
5
|
import { MIN_TOUCH_DISTANCE_PIXELS, pan_range_by_pixels, PINCH_ZOOM_THRESHOLD, remove_drag_listeners, snapshot_ranges, zoom_range_by_factor, } from './interactions';
|
|
6
|
+
import { point_in_rect } from './layout';
|
|
6
7
|
const AXES = [`x`, `x2`, `y`, `y2`];
|
|
7
8
|
export function create_pan_zoom(opts) {
|
|
8
9
|
// Rect-zoom drag state
|
|
9
|
-
let drag_state = $state(
|
|
10
|
+
let drag_state = $state(null);
|
|
10
11
|
// Pan state
|
|
11
12
|
let is_focused = $state(false);
|
|
12
13
|
let shift_held = $state(false);
|
|
13
14
|
let pan_drag_state = $state(null);
|
|
14
15
|
let touch_state = $state(null);
|
|
15
16
|
const cancel_rect_drag = () => {
|
|
16
|
-
drag_state =
|
|
17
|
+
drag_state = null;
|
|
17
18
|
if (typeof window !== `undefined`) {
|
|
18
19
|
window.removeEventListener(`mousemove`, on_window_mouse_move);
|
|
19
20
|
window.removeEventListener(`mouseup`, on_window_mouse_up);
|
|
@@ -21,7 +22,7 @@ export function create_pan_zoom(opts) {
|
|
|
21
22
|
}
|
|
22
23
|
};
|
|
23
24
|
const on_window_mouse_move = (evt) => {
|
|
24
|
-
if (!drag_state
|
|
25
|
+
if (!drag_state)
|
|
25
26
|
return;
|
|
26
27
|
const coords = {
|
|
27
28
|
x: evt.clientX - drag_state.bounds.left,
|
|
@@ -35,7 +36,7 @@ export function create_pan_zoom(opts) {
|
|
|
35
36
|
opts.on_drag_move?.(coords, inside_svg);
|
|
36
37
|
};
|
|
37
38
|
const on_window_mouse_up = () => {
|
|
38
|
-
if (drag_state
|
|
39
|
+
if (drag_state) {
|
|
39
40
|
// Ignore minuscule drag rects (e.g. accidental clicks)
|
|
40
41
|
const dx = Math.abs(drag_state.start.x - drag_state.current.x);
|
|
41
42
|
const dy = Math.abs(drag_state.start.y - drag_state.current.y);
|
|
@@ -47,7 +48,7 @@ export function create_pan_zoom(opts) {
|
|
|
47
48
|
// Pan/zoom all four axes from an interaction-start snapshot, each in its own
|
|
48
49
|
// scale's transform space (log axes pan by a constant factor, linear by a shift)
|
|
49
50
|
const pan_all_axes = (init, dx_px, dy_px) => {
|
|
50
|
-
const dims = opts.
|
|
51
|
+
const dims = opts.plot_bounds();
|
|
51
52
|
for (const axis of AXES) {
|
|
52
53
|
const horizontal = axis === `x` || axis === `x2`;
|
|
53
54
|
opts.set_range(axis, pan_range_by_pixels(init[`initial_${axis}_range`], horizontal ? dx_px : dy_px, horizontal ? dims.width : dims.height, opts.scale_type(axis)));
|
|
@@ -72,9 +73,17 @@ export function create_pan_zoom(opts) {
|
|
|
72
73
|
window.removeEventListener(`mouseup`, on_pan_end);
|
|
73
74
|
};
|
|
74
75
|
const on_mouse_down = (evt) => {
|
|
76
|
+
if (evt.button !== 0)
|
|
77
|
+
return;
|
|
75
78
|
const svg = opts.svg();
|
|
76
79
|
if (!svg)
|
|
77
80
|
return;
|
|
81
|
+
// The SVG also contains axes, tick labels, and titles. Only the padded data
|
|
82
|
+
// rectangle may start a drag interaction.
|
|
83
|
+
const svg_bounds = svg.getBoundingClientRect();
|
|
84
|
+
const coords = { x: evt.clientX - svg_bounds.left, y: evt.clientY - svg_bounds.top };
|
|
85
|
+
if (!point_in_rect(coords, opts.plot_bounds()))
|
|
86
|
+
return;
|
|
78
87
|
// Shift+drag pans (when enabled); plain drag draws the zoom rect
|
|
79
88
|
const pan_enabled = opts.pan()?.enabled !== false;
|
|
80
89
|
if (pan_enabled && evt.shiftKey) {
|
|
@@ -89,9 +98,7 @@ export function create_pan_zoom(opts) {
|
|
|
89
98
|
return;
|
|
90
99
|
}
|
|
91
100
|
// Cache bounds at drag start so window mousemove can compute relative coords
|
|
92
|
-
|
|
93
|
-
const coords = { x: evt.clientX - bounds.left, y: evt.clientY - bounds.top };
|
|
94
|
-
drag_state = { start: coords, current: coords, bounds };
|
|
101
|
+
drag_state = { start: coords, current: coords, bounds: svg_bounds };
|
|
95
102
|
window.addEventListener(`mousemove`, on_window_mouse_move);
|
|
96
103
|
window.addEventListener(`mouseup`, on_window_mouse_up);
|
|
97
104
|
document.body.style.cursor = `crosshair`;
|
|
@@ -104,7 +111,7 @@ export function create_pan_zoom(opts) {
|
|
|
104
111
|
if (pan_cfg?.enabled === false || !is_focused || !shift_held)
|
|
105
112
|
return;
|
|
106
113
|
evt.preventDefault();
|
|
107
|
-
const dims = opts.
|
|
114
|
+
const dims = opts.plot_bounds();
|
|
108
115
|
const sensitivity = pan_cfg?.wheel_sensitivity ?? 1;
|
|
109
116
|
const ranges = opts.ranges();
|
|
110
117
|
// Pan along the dominant wheel direction
|
|
@@ -125,12 +132,22 @@ export function create_pan_zoom(opts) {
|
|
|
125
132
|
const touch_enabled = pan_cfg?.enabled !== false && pan_cfg?.touch_enabled !== false;
|
|
126
133
|
if (!touch_enabled || evt.touches.length !== 2)
|
|
127
134
|
return;
|
|
135
|
+
const svg_bounds = opts.svg()?.getBoundingClientRect();
|
|
136
|
+
if (!svg_bounds)
|
|
137
|
+
return;
|
|
138
|
+
const plot_bounds = opts.plot_bounds();
|
|
139
|
+
const start_touches = Array.from(evt.touches).map((touch) => ({
|
|
140
|
+
x: touch.clientX,
|
|
141
|
+
y: touch.clientY,
|
|
142
|
+
}));
|
|
143
|
+
// Every contact must start in the data rectangle; touching an axis or label
|
|
144
|
+
// must not arm a gesture that later moves into the plot.
|
|
145
|
+
const starts_outside_plot = start_touches.some((touch) => !point_in_rect({ x: touch.x - svg_bounds.left, y: touch.y - svg_bounds.top }, plot_bounds));
|
|
146
|
+
if (starts_outside_plot)
|
|
147
|
+
return;
|
|
128
148
|
evt.preventDefault();
|
|
129
149
|
touch_state = {
|
|
130
|
-
start_touches
|
|
131
|
-
x: touch.clientX,
|
|
132
|
-
y: touch.clientY,
|
|
133
|
-
})),
|
|
150
|
+
start_touches,
|
|
134
151
|
...snapshot_ranges(opts.ranges()),
|
|
135
152
|
};
|
|
136
153
|
};
|
|
@@ -168,7 +185,7 @@ export function create_pan_zoom(opts) {
|
|
|
168
185
|
const on_key_down = (evt) => {
|
|
169
186
|
if (evt.target !== evt.currentTarget)
|
|
170
187
|
return;
|
|
171
|
-
if (evt.key === `Escape` && drag_state
|
|
188
|
+
if (evt.key === `Escape` && drag_state)
|
|
172
189
|
cancel_rect_drag();
|
|
173
190
|
if ([`Enter`, ` `].includes(evt.key)) {
|
|
174
191
|
evt.preventDefault();
|
|
@@ -177,10 +194,10 @@ export function create_pan_zoom(opts) {
|
|
|
177
194
|
};
|
|
178
195
|
return {
|
|
179
196
|
get drag_start() {
|
|
180
|
-
return drag_state
|
|
197
|
+
return drag_state?.start ?? null;
|
|
181
198
|
},
|
|
182
199
|
get drag_current() {
|
|
183
|
-
return drag_state
|
|
200
|
+
return drag_state?.current ?? null;
|
|
184
201
|
},
|
|
185
202
|
get is_pan_dragging() {
|
|
186
203
|
return pan_drag_state !== null;
|
|
@@ -213,7 +230,7 @@ export function create_pan_zoom(opts) {
|
|
|
213
230
|
// stuck cursor). Safe during SSR teardown, where window/document don't exist.
|
|
214
231
|
destroy: () => {
|
|
215
232
|
remove_drag_listeners([on_window_mouse_move, on_pan_move], [on_window_mouse_up, on_pan_end]);
|
|
216
|
-
drag_state =
|
|
233
|
+
drag_state = null;
|
|
217
234
|
pan_drag_state = null;
|
|
218
235
|
touch_state = null;
|
|
219
236
|
},
|
|
@@ -14,8 +14,11 @@ export declare function create_placed_tween(opts: {
|
|
|
14
14
|
tween?: () => TweenOptions<Point> | undefined;
|
|
15
15
|
suspended?: () => boolean;
|
|
16
16
|
manual_position?: () => Point | null;
|
|
17
|
+
on_element_resize?: () => void;
|
|
18
|
+
placement_revision?: () => unknown;
|
|
17
19
|
}): {
|
|
18
20
|
coords: Tween<Point>;
|
|
21
|
+
placed: () => boolean;
|
|
19
22
|
set_locked: (locked: boolean) => void;
|
|
20
23
|
};
|
|
21
24
|
export {};
|
|
@@ -5,9 +5,9 @@ import { Tween } from 'svelte/motion';
|
|
|
5
5
|
const HOVER_DEBOUNCE_MS = 300;
|
|
6
6
|
// Tweened position for an auto-placed plot element with the stability gating shared
|
|
7
7
|
// by BarPlot/BoxPlot/Histogram/ScatterPlot legends: snap (no animation) on first
|
|
8
|
-
// placement, follow container resizes, stay put while hovered (debounced
|
|
9
|
-
// and track placement continuously only when `responsive`. Creates
|
|
10
|
-
// must be called during component init.
|
|
8
|
+
// placement, follow container/element resizes, stay put while hovered (debounced
|
|
9
|
+
// hover lock), and track placement continuously only when `responsive`. Creates
|
|
10
|
+
// $effects, so it must be called during component init.
|
|
11
11
|
export function create_placed_tween(opts) {
|
|
12
12
|
// Hover lock: placement updates pause while the element is hovered; release is
|
|
13
13
|
// debounced so brief mouse-outs don't cause jumps
|
|
@@ -22,16 +22,43 @@ export function create_placed_tween(opts) {
|
|
|
22
22
|
else
|
|
23
23
|
unlock_timeout = setTimeout(() => (hover_locked = false), HOVER_DEBOUNCE_MS);
|
|
24
24
|
};
|
|
25
|
-
//
|
|
26
|
-
// don't depend on re-running when they flip
|
|
25
|
+
// Previous plot dimensions stay plain; they only classify the current effect run.
|
|
27
26
|
let prev_dims = null;
|
|
28
|
-
let
|
|
27
|
+
let element_size_revision = $state(0);
|
|
28
|
+
let prev_element_size_revision = 0;
|
|
29
|
+
let previous_element;
|
|
30
|
+
let previous_placement_revision;
|
|
31
|
+
let placed = $state(false);
|
|
29
32
|
const coords = new Tween({ x: 0, y: 0 }, untrack(() => ({ duration: 400, ...opts.tween?.() })));
|
|
30
33
|
// Clear pending unlock timeout to prevent state updates after unmount
|
|
31
34
|
$effect(() => () => {
|
|
32
35
|
if (unlock_timeout)
|
|
33
36
|
clearTimeout(unlock_timeout);
|
|
34
37
|
});
|
|
38
|
+
// Decoration content (title, orientation, tick labels) can resize without the
|
|
39
|
+
// plot dimensions changing. Observe it so frozen placements are recomputed.
|
|
40
|
+
$effect(() => {
|
|
41
|
+
const element = opts.element() ?? null;
|
|
42
|
+
if (previous_element !== undefined && previous_element !== element) {
|
|
43
|
+
element_size_revision += 1;
|
|
44
|
+
if (!element)
|
|
45
|
+
placed = false;
|
|
46
|
+
}
|
|
47
|
+
previous_element = element;
|
|
48
|
+
if (!element || typeof ResizeObserver === `undefined`)
|
|
49
|
+
return undefined;
|
|
50
|
+
const observed_elements = new WeakSet();
|
|
51
|
+
const observer = new ResizeObserver((entries) => {
|
|
52
|
+
const size_changed = entries.some(({ target }) => observed_elements.has(target));
|
|
53
|
+
for (const { target } of entries)
|
|
54
|
+
observed_elements.add(target);
|
|
55
|
+
if (size_changed)
|
|
56
|
+
element_size_revision += 1;
|
|
57
|
+
});
|
|
58
|
+
for (const target of [element, ...element.querySelectorAll(`*`)])
|
|
59
|
+
observer.observe(target);
|
|
60
|
+
return () => observer.disconnect();
|
|
61
|
+
});
|
|
35
62
|
// Update position with stability checks
|
|
36
63
|
$effect(() => {
|
|
37
64
|
const { width, height } = opts.dims();
|
|
@@ -43,26 +70,46 @@ export function create_placed_tween(opts) {
|
|
|
43
70
|
if (manual) {
|
|
44
71
|
// Immediate update (no animation) for manually positioned elements
|
|
45
72
|
void coords.set({ x: manual.x, y: manual.y }, { duration: 0 });
|
|
46
|
-
|
|
73
|
+
if (opts.element())
|
|
74
|
+
placed = true;
|
|
47
75
|
}
|
|
48
|
-
|
|
49
|
-
if (
|
|
76
|
+
// Leave resize revisions pending so unlock recomputes the latest placement.
|
|
77
|
+
if (hover_locked)
|
|
50
78
|
return;
|
|
51
79
|
// Track dimensions for resize detection
|
|
52
80
|
const dims_changed = !prev_dims || prev_dims.width !== width || prev_dims.height !== height;
|
|
53
81
|
if (dims_changed)
|
|
54
82
|
prev_dims = { width, height };
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
83
|
+
const element_size_changed = prev_element_size_revision !== element_size_revision;
|
|
84
|
+
prev_element_size_revision = element_size_revision;
|
|
85
|
+
if (element_size_changed)
|
|
86
|
+
opts.on_element_resize?.();
|
|
87
|
+
if (manual)
|
|
88
|
+
return;
|
|
89
|
+
const placement_revision = opts.placement_revision?.();
|
|
90
|
+
const placement_invalidated = !Object.is(previous_placement_revision, placement_revision);
|
|
91
|
+
previous_placement_revision = placement_revision;
|
|
92
|
+
const responsive = opts.responsive();
|
|
93
|
+
// A non-responsive tween tracks `placed` once so it immediately reruns and
|
|
94
|
+
// unsubscribes from the expensive placement chain after its first placement.
|
|
95
|
+
const has_initial_placement = responsive ? untrack(() => placed) : placed;
|
|
96
|
+
// Skip expensive DOM placement before evaluating it: non-responsive
|
|
97
|
+
// elements stay fixed after their initial placement until the plot resizes.
|
|
98
|
+
if (!dims_changed &&
|
|
99
|
+
!element_size_changed &&
|
|
100
|
+
!placement_invalidated &&
|
|
101
|
+
!responsive &&
|
|
102
|
+
has_initial_placement)
|
|
103
|
+
return;
|
|
104
|
+
const placement = opts.placement();
|
|
105
|
+
if (!placement)
|
|
106
|
+
return;
|
|
107
|
+
void coords.set({ x: placement.x, y: placement.y },
|
|
108
|
+
// Skip animation on initial placement to avoid jump from (0, 0)
|
|
109
|
+
has_initial_placement ? undefined : { duration: 0 });
|
|
110
|
+
// Only lock position after the element has an actual measured size
|
|
111
|
+
if (opts.element())
|
|
112
|
+
placed = true;
|
|
66
113
|
});
|
|
67
|
-
return { coords, set_locked };
|
|
114
|
+
return { coords, placed: () => placed, set_locked };
|
|
68
115
|
}
|
package/dist/plot/core/svg.js
CHANGED
|
@@ -29,7 +29,6 @@ export function violin_path(grid_px, half_offsets_px, center, side, orient) {
|
|
|
29
29
|
}
|
|
30
30
|
return `M${pts.map(([x_pos, y_pos]) => `${x_pos},${y_pos}`).join(`L`)}Z`;
|
|
31
31
|
}
|
|
32
|
-
//
|
|
33
32
|
// Generate SVG path for a bar with rounded corners on the "free" end (away from axis).
|
|
34
33
|
// For vertical bars, rounds top corners. For horizontal bars, rounds right corners.
|
|
35
34
|
export function bar_path(x, y, w, h, r, vertical = true) {
|
|
@@ -36,6 +36,7 @@ export declare function compute_node_infos<Metadata>(arcs: readonly PositionedAr
|
|
|
36
36
|
contrast: (fill: string) => string;
|
|
37
37
|
clickable?: (arc: PositionedArc<Metadata>) => boolean;
|
|
38
38
|
}): HierarchyNodeInfo[];
|
|
39
|
+
export declare const observe_height: (on_height: (px: number) => void) => (element: Element) => () => void;
|
|
39
40
|
export declare function svg_label_font(svg_element: SVGSVGElement | null): string;
|
|
40
41
|
export declare const pointer_pos: (event: MouseEvent | FocusEvent | undefined, svg_element: SVGSVGElement | null) => {
|
|
41
42
|
x: number;
|
|
@@ -145,6 +145,18 @@ export function compute_node_infos(arcs, opts) {
|
|
|
145
145
|
};
|
|
146
146
|
});
|
|
147
147
|
}
|
|
148
|
+
// Attachment factory reporting an element's rendered height (immediately, on
|
|
149
|
+
// resize, and 0 on unmount) — used to reserve chart space below the cells/arcs
|
|
150
|
+
// for the colorbar without an extra measuring wrapper div.
|
|
151
|
+
export const observe_height = (on_height) => (element) => {
|
|
152
|
+
const observer = new ResizeObserver(() => on_height(element.clientHeight));
|
|
153
|
+
observer.observe(element);
|
|
154
|
+
on_height(element.clientHeight);
|
|
155
|
+
return () => {
|
|
156
|
+
observer.disconnect();
|
|
157
|
+
on_height(0); // release the reserved space when the element unmounts
|
|
158
|
+
};
|
|
159
|
+
};
|
|
148
160
|
// The font node labels actually render in (respects the chart's font-size CSS
|
|
149
161
|
// var instead of assuming 11px), for canvas-measured label fitting
|
|
150
162
|
export function svg_label_font(svg_element) {
|
|
@@ -88,12 +88,13 @@ const EXPORT_INLINE_STYLES = [
|
|
|
88
88
|
`font-weight`,
|
|
89
89
|
`opacity`,
|
|
90
90
|
];
|
|
91
|
+
const EXPORT_OPTIONS = { viewbox_padding: `stroke` };
|
|
91
92
|
export function export_hierarchy_chart(svg_element, base_filename, format) {
|
|
92
93
|
if (!svg_element)
|
|
93
94
|
return;
|
|
94
95
|
const filename = `${base_filename}.${format}`;
|
|
95
96
|
if (format === `svg`)
|
|
96
|
-
export_svg_as_svg(svg_element, filename, EXPORT_INLINE_STYLES);
|
|
97
|
+
export_svg_as_svg(svg_element, filename, EXPORT_INLINE_STYLES, EXPORT_OPTIONS);
|
|
97
98
|
else
|
|
98
|
-
export_svg_as_png(svg_element, filename, 150, EXPORT_INLINE_STYLES);
|
|
99
|
+
export_svg_as_png(svg_element, filename, 150, EXPORT_INLINE_STYLES, EXPORT_OPTIONS);
|
|
99
100
|
}
|
|
@@ -398,10 +398,13 @@
|
|
|
398
398
|
base_pad = new_pad
|
|
399
399
|
})
|
|
400
400
|
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
401
|
+
let legend_size_revision = $state(0)
|
|
402
|
+
const legend_footprint = $derived.by(() => {
|
|
403
|
+
void legend_size_revision
|
|
404
|
+
return measured_footprint(legend_element, { width: 120, height: 60 })
|
|
405
|
+
})
|
|
404
406
|
const legend_has_explicit_pos = $derived(has_explicit_position(legend?.style))
|
|
407
|
+
const should_show_legend = $derived(show_legend && legend != null && series.length > 1)
|
|
405
408
|
|
|
406
409
|
// Obstacle field in normalized [0,1] plot coords (y=0 at top). Each filled bar is modeled as a
|
|
407
410
|
// vertical segment (top -> baseline) so the legend can't hide inside a tall bar. Built from
|
|
@@ -431,7 +434,7 @@
|
|
|
431
434
|
})
|
|
432
435
|
|
|
433
436
|
// Move the legend to the bottom margin when no interior spot avoids the bars
|
|
434
|
-
const decor = $derived
|
|
437
|
+
const decor = $derived(
|
|
435
438
|
place_decorations({
|
|
436
439
|
base_pad,
|
|
437
440
|
width,
|
|
@@ -439,11 +442,7 @@
|
|
|
439
442
|
obstacles_norm,
|
|
440
443
|
// gate on legend_element (the render signal) not legend_data, whose entries can read pad
|
|
441
444
|
legend:
|
|
442
|
-
|
|
443
|
-
legend != null &&
|
|
444
|
-
series.length > 1 &&
|
|
445
|
-
legend_element != null &&
|
|
446
|
-
!legend_has_explicit_pos
|
|
445
|
+
should_show_legend && legend_element != null && !legend_has_explicit_pos
|
|
447
446
|
? { footprint: legend_footprint, clearance: legend?.axis_clearance }
|
|
448
447
|
: null,
|
|
449
448
|
}),
|
|
@@ -472,9 +471,6 @@
|
|
|
472
471
|
const marginal_has_axis = $derived(
|
|
473
472
|
marginal_axis_presence(x2_series.length > 0, y2_series.length > 0),
|
|
474
473
|
)
|
|
475
|
-
const legend_auto_outside = $derived(decor.legend_outside)
|
|
476
|
-
const legend_outside_x = $derived(decor.legend_pos.x)
|
|
477
|
-
const legend_outside_y = $derived(decor.legend_pos.y)
|
|
478
474
|
|
|
479
475
|
// Scales and data (x/x2 share the horizontal pixel span, y/y2 the inverted vertical one)
|
|
480
476
|
let scales = $derived(
|
|
@@ -531,7 +527,6 @@
|
|
|
531
527
|
// Cache measured tick-label widths so expensive text measurement only runs
|
|
532
528
|
// when tick values/format change, not on every template rerender.
|
|
533
529
|
let tick_label_widths = $derived({
|
|
534
|
-
x2_max: measure_max_tick_width(ticks.x2, final_x2_axis.format ?? ``),
|
|
535
530
|
y_max: measure_max_tick_width(ticks.y, final_y_axis.format ?? ``),
|
|
536
531
|
y2_max: measure_max_tick_width(ticks.y2, final_y2_axis.format ?? ``),
|
|
537
532
|
})
|
|
@@ -562,14 +557,13 @@
|
|
|
562
557
|
})
|
|
563
558
|
|
|
564
559
|
// Calculate best legend placement using continuous grid sampling
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
if (!should_place || !width || !height) return null
|
|
560
|
+
const get_legend_placement = () => {
|
|
561
|
+
if (!should_show_legend || !width || !height) return null
|
|
568
562
|
|
|
569
563
|
const plot_width = width - pad.l - pad.r
|
|
570
564
|
const plot_height = height - pad.t - pad.b
|
|
571
565
|
|
|
572
|
-
|
|
566
|
+
return compute_element_placement({
|
|
573
567
|
plot_bounds: { x: pad.l, y: pad.t, width: plot_width, height: plot_height },
|
|
574
568
|
element: legend_element,
|
|
575
569
|
element_size: { width: 120, height: 60 }, // fallback before first render
|
|
@@ -577,17 +571,16 @@
|
|
|
577
571
|
exclude_rects: [],
|
|
578
572
|
points: hist_points_for_placement,
|
|
579
573
|
})
|
|
580
|
-
|
|
581
|
-
return result
|
|
582
|
-
})
|
|
574
|
+
}
|
|
583
575
|
|
|
584
576
|
// Tweened legend coordinates with shared placement stability gating
|
|
585
577
|
const legend_tween = create_placed_tween({
|
|
586
|
-
placement:
|
|
578
|
+
placement: get_legend_placement,
|
|
587
579
|
dims: () => ({ width, height }),
|
|
588
580
|
responsive: () => legend?.responsive ?? false,
|
|
589
581
|
element: () => legend_element,
|
|
590
582
|
tween: () => legend?.tween,
|
|
583
|
+
on_element_resize: () => (legend_size_revision += 1),
|
|
591
584
|
})
|
|
592
585
|
|
|
593
586
|
// Shared pan/zoom/touch/drag-rect interaction controller
|
|
@@ -597,7 +590,9 @@
|
|
|
597
590
|
({ x: final_x_axis, x2: final_x2_axis, y: final_y_axis, y2: final_y2_axis })[axis]
|
|
598
591
|
.scale_type,
|
|
599
592
|
// Clamp to at least 1 to avoid Infinity deltas when padding equals container size
|
|
600
|
-
|
|
593
|
+
plot_bounds: () => ({
|
|
594
|
+
x: pad.l,
|
|
595
|
+
y: pad.t,
|
|
601
596
|
width: Math.max(1, width - pad.l - pad.r),
|
|
602
597
|
height: Math.max(1, height - pad.t - pad.b),
|
|
603
598
|
}),
|
|
@@ -1042,11 +1037,11 @@
|
|
|
1042
1037
|
/>
|
|
1043
1038
|
{/if}
|
|
1044
1039
|
|
|
1045
|
-
{#if
|
|
1040
|
+
{#if should_show_legend && legend}
|
|
1046
1041
|
{@const legend_pos = placed_coords(
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1042
|
+
decor.legend_outside,
|
|
1043
|
+
decor.legend_pos,
|
|
1044
|
+
legend_tween.placed(),
|
|
1050
1045
|
legend_tween.coords.current,
|
|
1051
1046
|
{ x: pad.l + 10, y: pad.t + 10 },
|
|
1052
1047
|
)}
|
|
@@ -20,11 +20,6 @@ function resolve_node_ref(ref, id_to_idx, n_nodes) {
|
|
|
20
20
|
}
|
|
21
21
|
throw new Error(`Sankey link references unknown node: ${JSON.stringify(ref)}`);
|
|
22
22
|
}
|
|
23
|
-
function resolve_numeric_node_ref(ref, n_nodes) {
|
|
24
|
-
if (Number.isInteger(ref) && ref >= 0 && ref < n_nodes)
|
|
25
|
-
return ref;
|
|
26
|
-
throw new Error(`Sankey link references unknown node: ${JSON.stringify(ref)}`);
|
|
27
|
-
}
|
|
28
23
|
// Build a SankeyData object from parallel flat arrays (e.g. the plotly/matbench
|
|
29
24
|
// `link.source`/`link.target`/`link.value` + `node.label` format).
|
|
30
25
|
export function sankey_from_links(source, target, value, labels) {
|
|
@@ -107,9 +102,18 @@ export function compute_sankey_layout(data, opts) {
|
|
|
107
102
|
}));
|
|
108
103
|
// Resolve a source/target ref to a node index: id/label lookup when any link uses
|
|
109
104
|
// non-numeric refs, otherwise a cheap numeric-range check (no map built).
|
|
110
|
-
const resolve_ref = (ref) =>
|
|
111
|
-
|
|
112
|
-
|
|
105
|
+
const resolve_ref = (ref) => {
|
|
106
|
+
if (needs_ref_lookup) {
|
|
107
|
+
return resolve_node_ref(ref, id_to_idx, data.nodes.length);
|
|
108
|
+
}
|
|
109
|
+
if (typeof ref === `number` &&
|
|
110
|
+
Number.isInteger(ref) &&
|
|
111
|
+
ref >= 0 &&
|
|
112
|
+
ref < data.nodes.length) {
|
|
113
|
+
return ref;
|
|
114
|
+
}
|
|
115
|
+
throw new Error(`Sankey link references unknown node: ${JSON.stringify(ref)}`);
|
|
116
|
+
};
|
|
113
117
|
const link_copies = data.links.map((link, idx) => ({
|
|
114
118
|
link_idx: idx,
|
|
115
119
|
color: link.color,
|
|
@@ -185,5 +189,5 @@ export function compute_sankey_layout(data, opts) {
|
|
|
185
189
|
}
|
|
186
190
|
return graph;
|
|
187
191
|
}
|
|
188
|
-
// Map a node alignment string to the d3-sankey alignment function
|
|
192
|
+
// Map a node alignment string to the d3-sankey alignment function
|
|
189
193
|
export const sankey_align_fn = (align) => ALIGN_FNS[align];
|