@opendata-ai/openchart-vanilla 2.9.1 → 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 +51 -14
- 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 +16 -4
- 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,7 +662,10 @@ 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;
|
|
@@ -667,7 +673,11 @@ var ZoomTransform = class _ZoomTransform {
|
|
|
667
673
|
const cx = (minX + maxX) / 2;
|
|
668
674
|
const tx = canvasW / 2 - cx * k;
|
|
669
675
|
const ty = padding - minY * k;
|
|
670
|
-
|
|
676
|
+
const contentHeight = graphH * k + padding * 2;
|
|
677
|
+
return {
|
|
678
|
+
transform: new _ZoomTransform(tx, ty, k),
|
|
679
|
+
contentHeight
|
|
680
|
+
};
|
|
671
681
|
}
|
|
672
682
|
/** Identity transform (no pan, no zoom). */
|
|
673
683
|
static identity() {
|
|
@@ -2474,6 +2484,7 @@ function createGraph(container, spec, options) {
|
|
|
2474
2484
|
let needsRender = false;
|
|
2475
2485
|
let isGesturing = false;
|
|
2476
2486
|
let gestureTimeout = null;
|
|
2487
|
+
let selfResizing = false;
|
|
2477
2488
|
function markGesture() {
|
|
2478
2489
|
isGesturing = true;
|
|
2479
2490
|
if (gestureTimeout !== null) clearTimeout(gestureTimeout);
|
|
@@ -2677,10 +2688,28 @@ function createGraph(container, spec, options) {
|
|
|
2677
2688
|
scheduleRender();
|
|
2678
2689
|
});
|
|
2679
2690
|
simulation.onSettled(() => {
|
|
2680
|
-
if (canvas && positionedNodes.length > 0 && interactionManager) {
|
|
2691
|
+
if (canvas && positionedNodes.length > 0 && interactionManager && renderer) {
|
|
2681
2692
|
const { width: cw, height: ch } = getCanvasDimensions();
|
|
2682
|
-
const fitTransform = ZoomTransform.fitBounds(
|
|
2693
|
+
const { transform: fitTransform, contentHeight } = ZoomTransform.fitBounds(
|
|
2694
|
+
positionedNodes,
|
|
2695
|
+
cw,
|
|
2696
|
+
ch
|
|
2697
|
+
);
|
|
2683
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
|
+
}
|
|
2684
2713
|
needsRender = true;
|
|
2685
2714
|
scheduleRender();
|
|
2686
2715
|
}
|
|
@@ -2858,7 +2887,7 @@ function createGraph(container, spec, options) {
|
|
|
2858
2887
|
function zoomToFit() {
|
|
2859
2888
|
if (destroyed || !interactionManager || positionedNodes.length === 0) return;
|
|
2860
2889
|
const { width: cw, height: ch } = getCanvasDimensions();
|
|
2861
|
-
const fitTransform = ZoomTransform.fitBounds(positionedNodes, cw, ch);
|
|
2890
|
+
const { transform: fitTransform } = ZoomTransform.fitBounds(positionedNodes, cw, ch);
|
|
2862
2891
|
interactionManager.setTransform(fitTransform);
|
|
2863
2892
|
needsRender = true;
|
|
2864
2893
|
scheduleRender();
|
|
@@ -2887,7 +2916,8 @@ function createGraph(container, spec, options) {
|
|
|
2887
2916
|
return [...selectedNodeIds];
|
|
2888
2917
|
}
|
|
2889
2918
|
function doResize() {
|
|
2890
|
-
if (destroyed || !canvas || !renderer || !wrapper) return;
|
|
2919
|
+
if (destroyed || !canvas || !renderer || !wrapper || selfResizing) return;
|
|
2920
|
+
container.style.height = "";
|
|
2891
2921
|
const { width, height } = getContainerDimensions();
|
|
2892
2922
|
const chromeHeight = chromeEl?.getBoundingClientRect().height || 0;
|
|
2893
2923
|
const canvasHeight = Math.max(height - chromeHeight, 200);
|
|
@@ -3680,13 +3710,19 @@ function renderLegend(parent, legend) {
|
|
|
3680
3710
|
entryG.setAttribute("role", "listitem");
|
|
3681
3711
|
entryG.setAttribute("data-legend-index", String(i));
|
|
3682
3712
|
entryG.setAttribute("data-legend-label", entry.label);
|
|
3683
|
-
|
|
3684
|
-
"
|
|
3685
|
-
|
|
3686
|
-
|
|
3687
|
-
|
|
3688
|
-
|
|
3689
|
-
|
|
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
|
+
}
|
|
3690
3726
|
}
|
|
3691
3727
|
if (entry.shape === "circle") {
|
|
3692
3728
|
const circle = createSVGElement("circle");
|
|
@@ -4538,6 +4574,7 @@ function wireLegendInteraction(svg, _layout, onLegendToggle, onEdit) {
|
|
|
4538
4574
|
const cleanups = [];
|
|
4539
4575
|
const hiddenSeries = /* @__PURE__ */ new Set();
|
|
4540
4576
|
for (const entry of legendEntries) {
|
|
4577
|
+
if (entry.getAttribute("data-legend-overflow") === "true") continue;
|
|
4541
4578
|
const handleClick = () => {
|
|
4542
4579
|
const label = entry.getAttribute("data-legend-label");
|
|
4543
4580
|
if (!label) return;
|