@opendata-ai/openchart-vanilla 2.9.0 → 2.10.0
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/index.js +52 -16
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/src/graph/__tests__/zoom.test.ts +12 -4
- package/src/graph/zoom.ts +20 -7
- package/src/graph-mount.ts +35 -4
- package/src/mount.ts +3 -0
- package/src/svg-renderer.ts +14 -8
package/dist/index.js
CHANGED
|
@@ -640,10 +640,13 @@ var ZoomTransform = class _ZoomTransform {
|
|
|
640
640
|
/**
|
|
641
641
|
* Compute a transform that fits all nodes within the given canvas
|
|
642
642
|
* dimensions with the specified padding.
|
|
643
|
+
*
|
|
644
|
+
* Returns the transform and the ideal content height (in screen pixels)
|
|
645
|
+
* so callers can shrink the canvas to eliminate dead space.
|
|
643
646
|
*/
|
|
644
647
|
static fitBounds(nodes, canvasW, canvasH, padding = 40) {
|
|
645
648
|
if (nodes.length === 0) {
|
|
646
|
-
return _ZoomTransform.identity();
|
|
649
|
+
return { transform: _ZoomTransform.identity(), contentHeight: canvasH };
|
|
647
650
|
}
|
|
648
651
|
let minX = Infinity;
|
|
649
652
|
let minY = Infinity;
|
|
@@ -659,16 +662,22 @@ var ZoomTransform = class _ZoomTransform {
|
|
|
659
662
|
const graphW = maxX - minX;
|
|
660
663
|
const graphH = maxY - minY;
|
|
661
664
|
if (graphW === 0 && graphH === 0) {
|
|
662
|
-
return
|
|
665
|
+
return {
|
|
666
|
+
transform: new _ZoomTransform(canvasW / 2 - minX, canvasH / 2 - minY, 1),
|
|
667
|
+
contentHeight: padding * 2
|
|
668
|
+
};
|
|
663
669
|
}
|
|
664
670
|
const availW = canvasW - padding * 2;
|
|
665
671
|
const availH = canvasH - padding * 2;
|
|
666
672
|
const k = Math.min(availW / graphW, availH / graphH);
|
|
667
673
|
const cx = (minX + maxX) / 2;
|
|
668
|
-
const cy = (minY + maxY) / 2;
|
|
669
674
|
const tx = canvasW / 2 - cx * k;
|
|
670
|
-
const ty =
|
|
671
|
-
|
|
675
|
+
const ty = padding - minY * k;
|
|
676
|
+
const contentHeight = graphH * k + padding * 2;
|
|
677
|
+
return {
|
|
678
|
+
transform: new _ZoomTransform(tx, ty, k),
|
|
679
|
+
contentHeight
|
|
680
|
+
};
|
|
672
681
|
}
|
|
673
682
|
/** Identity transform (no pan, no zoom). */
|
|
674
683
|
static identity() {
|
|
@@ -2475,6 +2484,7 @@ function createGraph(container, spec, options) {
|
|
|
2475
2484
|
let needsRender = false;
|
|
2476
2485
|
let isGesturing = false;
|
|
2477
2486
|
let gestureTimeout = null;
|
|
2487
|
+
let selfResizing = false;
|
|
2478
2488
|
function markGesture() {
|
|
2479
2489
|
isGesturing = true;
|
|
2480
2490
|
if (gestureTimeout !== null) clearTimeout(gestureTimeout);
|
|
@@ -2678,10 +2688,28 @@ function createGraph(container, spec, options) {
|
|
|
2678
2688
|
scheduleRender();
|
|
2679
2689
|
});
|
|
2680
2690
|
simulation.onSettled(() => {
|
|
2681
|
-
if (canvas && positionedNodes.length > 0 && interactionManager) {
|
|
2691
|
+
if (canvas && positionedNodes.length > 0 && interactionManager && renderer) {
|
|
2682
2692
|
const { width: cw, height: ch } = getCanvasDimensions();
|
|
2683
|
-
const fitTransform = ZoomTransform.fitBounds(
|
|
2693
|
+
const { transform: fitTransform, contentHeight } = ZoomTransform.fitBounds(
|
|
2694
|
+
positionedNodes,
|
|
2695
|
+
cw,
|
|
2696
|
+
ch
|
|
2697
|
+
);
|
|
2684
2698
|
interactionManager.setTransform(fitTransform);
|
|
2699
|
+
const chromeH = chromeEl?.getBoundingClientRect().height || 0;
|
|
2700
|
+
const totalContentHeight = Math.ceil(contentHeight) + chromeH;
|
|
2701
|
+
const containerH = container.getBoundingClientRect().height;
|
|
2702
|
+
if (totalContentHeight < containerH) {
|
|
2703
|
+
selfResizing = true;
|
|
2704
|
+
const targetCanvasH = Math.ceil(contentHeight);
|
|
2705
|
+
renderer.resize(cw, targetCanvasH);
|
|
2706
|
+
const refit = ZoomTransform.fitBounds(positionedNodes, cw, targetCanvasH);
|
|
2707
|
+
interactionManager.setTransform(refit.transform);
|
|
2708
|
+
container.style.height = "fit-content";
|
|
2709
|
+
setTimeout(() => {
|
|
2710
|
+
selfResizing = false;
|
|
2711
|
+
}, 100);
|
|
2712
|
+
}
|
|
2685
2713
|
needsRender = true;
|
|
2686
2714
|
scheduleRender();
|
|
2687
2715
|
}
|
|
@@ -2859,7 +2887,7 @@ function createGraph(container, spec, options) {
|
|
|
2859
2887
|
function zoomToFit() {
|
|
2860
2888
|
if (destroyed || !interactionManager || positionedNodes.length === 0) return;
|
|
2861
2889
|
const { width: cw, height: ch } = getCanvasDimensions();
|
|
2862
|
-
const fitTransform = ZoomTransform.fitBounds(positionedNodes, cw, ch);
|
|
2890
|
+
const { transform: fitTransform } = ZoomTransform.fitBounds(positionedNodes, cw, ch);
|
|
2863
2891
|
interactionManager.setTransform(fitTransform);
|
|
2864
2892
|
needsRender = true;
|
|
2865
2893
|
scheduleRender();
|
|
@@ -2888,7 +2916,8 @@ function createGraph(container, spec, options) {
|
|
|
2888
2916
|
return [...selectedNodeIds];
|
|
2889
2917
|
}
|
|
2890
2918
|
function doResize() {
|
|
2891
|
-
if (destroyed || !canvas || !renderer || !wrapper) return;
|
|
2919
|
+
if (destroyed || !canvas || !renderer || !wrapper || selfResizing) return;
|
|
2920
|
+
container.style.height = "";
|
|
2892
2921
|
const { width, height } = getContainerDimensions();
|
|
2893
2922
|
const chromeHeight = chromeEl?.getBoundingClientRect().height || 0;
|
|
2894
2923
|
const canvasHeight = Math.max(height - chromeHeight, 200);
|
|
@@ -3681,13 +3710,19 @@ function renderLegend(parent, legend) {
|
|
|
3681
3710
|
entryG.setAttribute("role", "listitem");
|
|
3682
3711
|
entryG.setAttribute("data-legend-index", String(i));
|
|
3683
3712
|
entryG.setAttribute("data-legend-label", entry.label);
|
|
3684
|
-
|
|
3685
|
-
"
|
|
3686
|
-
|
|
3687
|
-
|
|
3688
|
-
|
|
3689
|
-
|
|
3690
|
-
|
|
3713
|
+
if (entry.overflow) {
|
|
3714
|
+
entryG.setAttribute("data-legend-overflow", "true");
|
|
3715
|
+
entryG.setAttribute("aria-label", entry.label);
|
|
3716
|
+
entryG.setAttribute("opacity", "0.5");
|
|
3717
|
+
} else {
|
|
3718
|
+
entryG.setAttribute(
|
|
3719
|
+
"aria-label",
|
|
3720
|
+
`${entry.label}: ${entry.active !== false ? "visible" : "hidden"}`
|
|
3721
|
+
);
|
|
3722
|
+
entryG.setAttribute("style", "cursor: pointer");
|
|
3723
|
+
if (entry.active === false) {
|
|
3724
|
+
entryG.setAttribute("opacity", "0.3");
|
|
3725
|
+
}
|
|
3691
3726
|
}
|
|
3692
3727
|
if (entry.shape === "circle") {
|
|
3693
3728
|
const circle = createSVGElement("circle");
|
|
@@ -4539,6 +4574,7 @@ function wireLegendInteraction(svg, _layout, onLegendToggle, onEdit) {
|
|
|
4539
4574
|
const cleanups = [];
|
|
4540
4575
|
const hiddenSeries = /* @__PURE__ */ new Set();
|
|
4541
4576
|
for (const entry of legendEntries) {
|
|
4577
|
+
if (entry.getAttribute("data-legend-overflow") === "true") continue;
|
|
4542
4578
|
const handleClick = () => {
|
|
4543
4579
|
const label = entry.getAttribute("data-legend-label");
|
|
4544
4580
|
if (!label) return;
|