@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 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 new _ZoomTransform(canvasW / 2 - minX, canvasH / 2 - minY, 1);
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 = canvasH / 2 - cy * k;
671
- return new _ZoomTransform(tx, ty, k);
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(positionedNodes, cw, ch);
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
- entryG.setAttribute(
3685
- "aria-label",
3686
- `${entry.label}: ${entry.active !== false ? "visible" : "hidden"}`
3687
- );
3688
- entryG.setAttribute("style", "cursor: pointer");
3689
- if (entry.active === false) {
3690
- entryG.setAttribute("opacity", "0.3");
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;