polly-graph 0.1.3 → 0.1.4
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.cjs +243 -182
- package/dist/index.css +221 -32
- package/dist/index.d.cts +22 -3
- package/dist/index.d.ts +22 -3
- package/dist/index.js +233 -182
- package/package.json +3 -2
package/dist/index.js
CHANGED
|
@@ -3671,41 +3671,44 @@ function y_default2(y3) {
|
|
|
3671
3671
|
}
|
|
3672
3672
|
|
|
3673
3673
|
// src/core/create-graph-layers.ts
|
|
3674
|
-
function createGraphLayers(
|
|
3675
|
-
|
|
3676
|
-
|
|
3677
|
-
|
|
3678
|
-
const
|
|
3674
|
+
function createGraphLayers(host) {
|
|
3675
|
+
host.innerHTML = "";
|
|
3676
|
+
const rootContainer = document.createElement("div");
|
|
3677
|
+
rootContainer.className = "pg-root";
|
|
3678
|
+
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
3679
|
+
svg.setAttribute("class", "pg-canvas");
|
|
3680
|
+
const overlay = document.createElement("div");
|
|
3681
|
+
overlay.className = "pg-overlay";
|
|
3682
|
+
rootContainer.appendChild(svg);
|
|
3683
|
+
rootContainer.appendChild(overlay);
|
|
3684
|
+
host.appendChild(rootContainer);
|
|
3685
|
+
const createGroup = (layerName) => {
|
|
3679
3686
|
const group = document.createElementNS("http://www.w3.org/2000/svg", "g");
|
|
3680
|
-
group.setAttribute("class",
|
|
3681
|
-
group.setAttribute("data-layer",
|
|
3687
|
+
group.setAttribute("class", `pg-layer-${layerName}`);
|
|
3688
|
+
group.setAttribute("data-layer", layerName);
|
|
3682
3689
|
return group;
|
|
3683
3690
|
};
|
|
3684
3691
|
const interactionLayer = createGroup("interaction-layer");
|
|
3685
3692
|
const interactionRect = document.createElementNS("http://www.w3.org/2000/svg", "rect");
|
|
3686
|
-
|
|
3687
|
-
|
|
3688
|
-
|
|
3689
|
-
height: "100%",
|
|
3690
|
-
fill: "transparent",
|
|
3691
|
-
"pointer-events": "all"
|
|
3692
|
-
};
|
|
3693
|
-
Object.entries(interactionAttributes).forEach(([key, value]) => {
|
|
3694
|
-
interactionRect.setAttribute(key, value);
|
|
3695
|
-
});
|
|
3693
|
+
interactionRect.setAttribute("class", "pg-interaction-surface");
|
|
3694
|
+
interactionRect.setAttribute("fill", "transparent");
|
|
3695
|
+
interactionRect.setAttribute("pointer-events", "all");
|
|
3696
3696
|
interactionLayer.appendChild(interactionRect);
|
|
3697
|
-
const
|
|
3697
|
+
const graphRoot = createGroup("viewport");
|
|
3698
3698
|
const layers = {
|
|
3699
|
+
svg,
|
|
3700
|
+
overlay,
|
|
3699
3701
|
interactionLayer,
|
|
3700
3702
|
interactionRect,
|
|
3701
|
-
root:
|
|
3703
|
+
root: graphRoot,
|
|
3704
|
+
// These keys now match your ctx.root.select('[data-layer="..."]') calls
|
|
3702
3705
|
links: createGroup("links"),
|
|
3703
3706
|
linkLabels: createGroup("link-labels"),
|
|
3704
3707
|
nodeRings: createGroup("node-rings"),
|
|
3705
3708
|
nodes: createGroup("nodes"),
|
|
3706
3709
|
nodeLabels: createGroup("node-labels")
|
|
3707
3710
|
};
|
|
3708
|
-
|
|
3711
|
+
graphRoot.append(
|
|
3709
3712
|
layers.links,
|
|
3710
3713
|
layers.linkLabels,
|
|
3711
3714
|
layers.nodeRings,
|
|
@@ -3713,7 +3716,7 @@ function createGraphLayers(svg) {
|
|
|
3713
3716
|
layers.nodeLabels
|
|
3714
3717
|
);
|
|
3715
3718
|
svg.appendChild(interactionLayer);
|
|
3716
|
-
svg.appendChild(
|
|
3719
|
+
svg.appendChild(graphRoot);
|
|
3717
3720
|
return layers;
|
|
3718
3721
|
}
|
|
3719
3722
|
|
|
@@ -3830,49 +3833,51 @@ function getControlIcon(icon) {
|
|
|
3830
3833
|
}
|
|
3831
3834
|
|
|
3832
3835
|
// src/controls/create-graph-controls.ts
|
|
3833
|
-
function createGraphControls(
|
|
3836
|
+
function createGraphControls(overlay, graph, config) {
|
|
3834
3837
|
let root2 = null;
|
|
3835
3838
|
function mount() {
|
|
3836
3839
|
if (!config.enabled) {
|
|
3837
3840
|
return;
|
|
3838
3841
|
}
|
|
3839
|
-
const parent = container.parentElement;
|
|
3840
|
-
if (!parent) {
|
|
3841
|
-
return;
|
|
3842
|
-
}
|
|
3843
3842
|
root2 = document.createElement("div");
|
|
3844
3843
|
root2.className = "pg-controls";
|
|
3845
|
-
|
|
3846
|
-
|
|
3844
|
+
const position = resolveControlsPosition(config.position);
|
|
3845
|
+
root2.classList.add(`pg-pos-${position}`);
|
|
3846
|
+
const orientation = resolveControlsOrientation(config.orientation);
|
|
3847
|
+
root2.classList.add(`pg-orient-${orientation}`);
|
|
3848
|
+
if (config.offset) {
|
|
3849
|
+
root2.style.setProperty("--pg-controls-offset-x", `${config.offset.x}px`);
|
|
3850
|
+
root2.style.setProperty("--pg-controls-offset-y", `${config.offset.y}px`);
|
|
3851
|
+
}
|
|
3847
3852
|
appendControls(root2, config, graph);
|
|
3848
|
-
|
|
3853
|
+
overlay.appendChild(root2);
|
|
3849
3854
|
}
|
|
3850
3855
|
function appendControls(root3, config2, graph2) {
|
|
3851
|
-
|
|
3852
|
-
|
|
3853
|
-
|
|
3854
|
-
|
|
3855
|
-
|
|
3856
|
-
|
|
3857
|
-
|
|
3858
|
-
|
|
3859
|
-
|
|
3860
|
-
|
|
3861
|
-
|
|
3862
|
-
}
|
|
3856
|
+
const actions = [
|
|
3857
|
+
{ key: "zoomIn", icon: "zoom-in", label: "Zoom in", fn: graph2.zoomIn.bind(graph2) },
|
|
3858
|
+
{ key: "zoomOut", icon: "zoom-out", label: "Zoom out", fn: graph2.zoomOut.bind(graph2) },
|
|
3859
|
+
{ key: "fit", icon: "fit", label: "Fit view", fn: graph2.fitView.bind(graph2) },
|
|
3860
|
+
{ key: "reset", icon: "reset", label: "Reset view", fn: graph2.resetView.bind(graph2) }
|
|
3861
|
+
];
|
|
3862
|
+
actions.forEach((action) => {
|
|
3863
|
+
if (shouldRenderControl(config2, action.key)) {
|
|
3864
|
+
root3.appendChild(createButton(action.icon, action.label, action.fn));
|
|
3865
|
+
}
|
|
3866
|
+
});
|
|
3863
3867
|
}
|
|
3864
3868
|
function createButton(type, label, onClick) {
|
|
3865
3869
|
const button = document.createElement("button");
|
|
3870
|
+
button.className = "pg-control-btn";
|
|
3866
3871
|
button.type = "button";
|
|
3867
3872
|
button.setAttribute("aria-label", label);
|
|
3868
3873
|
const wrapper = document.createElement("div");
|
|
3874
|
+
wrapper.className = "pg-icon-wrapper";
|
|
3869
3875
|
wrapper.innerHTML = getControlIcon(type);
|
|
3870
3876
|
const svg = wrapper.querySelector("svg");
|
|
3871
|
-
if (
|
|
3872
|
-
|
|
3877
|
+
if (svg) {
|
|
3878
|
+
svg.classList.add("pg-icon");
|
|
3879
|
+
button.appendChild(svg);
|
|
3873
3880
|
}
|
|
3874
|
-
svg.classList.add("pg-icon");
|
|
3875
|
-
button.appendChild(svg);
|
|
3876
3881
|
button.addEventListener("click", onClick);
|
|
3877
3882
|
return button;
|
|
3878
3883
|
}
|
|
@@ -3880,39 +3885,70 @@ function createGraphControls(container, graph, config) {
|
|
|
3880
3885
|
if (!root2) {
|
|
3881
3886
|
return;
|
|
3882
3887
|
}
|
|
3883
|
-
|
|
3884
|
-
|
|
3888
|
+
if (root2.parentNode === overlay) {
|
|
3889
|
+
overlay.removeChild(root2);
|
|
3890
|
+
}
|
|
3885
3891
|
root2 = null;
|
|
3886
3892
|
}
|
|
3887
3893
|
return { mount, destroy };
|
|
3888
3894
|
}
|
|
3889
|
-
|
|
3890
|
-
|
|
3891
|
-
|
|
3892
|
-
|
|
3893
|
-
|
|
3894
|
-
|
|
3895
|
-
|
|
3896
|
-
|
|
3897
|
-
|
|
3898
|
-
|
|
3899
|
-
el.style.right = `${offset.x}px`;
|
|
3900
|
-
el.style.bottom = `${offset.y}px`;
|
|
3901
|
-
break;
|
|
3902
|
-
case "top-left":
|
|
3903
|
-
el.style.left = `${offset.x}px`;
|
|
3904
|
-
el.style.top = `${offset.y}px`;
|
|
3905
|
-
break;
|
|
3906
|
-
case "top-right":
|
|
3907
|
-
el.style.right = `${offset.x}px`;
|
|
3908
|
-
el.style.top = `${offset.y}px`;
|
|
3909
|
-
break;
|
|
3895
|
+
|
|
3896
|
+
// src/assets/caret.svg?raw
|
|
3897
|
+
var caret_default = '<svg\n xmlns="http://www.w3.org/2000/svg"\n viewBox="0 0 24 24"\n fill="none"\n stroke="currentColor"\n stroke-width="2"\n stroke-linecap="round"\n stroke-linejoin="round"\n>\n <path d="M9 20L16.5 12L9 4" />\n</svg>';
|
|
3898
|
+
|
|
3899
|
+
// src/legends/graph-legend-icon.ts
|
|
3900
|
+
var LEGEND_ICON_MAP = { caret: caret_default };
|
|
3901
|
+
function getLegendIcon(icon) {
|
|
3902
|
+
const raw = LEGEND_ICON_MAP[icon];
|
|
3903
|
+
if (!raw) {
|
|
3904
|
+
throw new Error(`Legend icon not found: ${icon}`);
|
|
3910
3905
|
}
|
|
3906
|
+
return raw.replace("<svg", '<svg class="pg-icon"');
|
|
3911
3907
|
}
|
|
3912
|
-
|
|
3913
|
-
|
|
3914
|
-
|
|
3915
|
-
|
|
3908
|
+
|
|
3909
|
+
// src/legends/create-graph-legends.ts
|
|
3910
|
+
function createGraphLegend(overlay, config) {
|
|
3911
|
+
const legendWrapper = document.createElement("div");
|
|
3912
|
+
legendWrapper.className = "pg-legend";
|
|
3913
|
+
const position = config.position || "bottom-right";
|
|
3914
|
+
legendWrapper.classList.add(`pg-pos-${position}`);
|
|
3915
|
+
if (config.defaultExpanded === false) {
|
|
3916
|
+
legendWrapper.classList.add("pg-is-collapsed");
|
|
3917
|
+
}
|
|
3918
|
+
if (config.collapsible) {
|
|
3919
|
+
const toggleBtn = document.createElement("button");
|
|
3920
|
+
toggleBtn.className = "pg-legend-toggle";
|
|
3921
|
+
toggleBtn.type = "button";
|
|
3922
|
+
toggleBtn.innerHTML = getLegendIcon("caret");
|
|
3923
|
+
toggleBtn.onclick = (e) => {
|
|
3924
|
+
e.stopPropagation();
|
|
3925
|
+
legendWrapper.classList.toggle("pg-is-collapsed");
|
|
3926
|
+
};
|
|
3927
|
+
legendWrapper.appendChild(toggleBtn);
|
|
3928
|
+
}
|
|
3929
|
+
const body = document.createElement("div");
|
|
3930
|
+
body.className = "pg-legend-body";
|
|
3931
|
+
const list = document.createElement("ul");
|
|
3932
|
+
list.className = "pg-legend-list";
|
|
3933
|
+
config.items.forEach((item) => {
|
|
3934
|
+
const listItem = document.createElement("li");
|
|
3935
|
+
listItem.className = "pg-legend-item";
|
|
3936
|
+
const swatch = document.createElement("span");
|
|
3937
|
+
swatch.className = `pg-legend-swatch is-${item.shape || "circle"}`;
|
|
3938
|
+
swatch.style.backgroundColor = item.color;
|
|
3939
|
+
const label = document.createElement("span");
|
|
3940
|
+
label.className = "pg-legend-label";
|
|
3941
|
+
label.innerText = item.label;
|
|
3942
|
+
listItem.appendChild(swatch);
|
|
3943
|
+
listItem.appendChild(label);
|
|
3944
|
+
list.appendChild(listItem);
|
|
3945
|
+
});
|
|
3946
|
+
body.appendChild(list);
|
|
3947
|
+
legendWrapper.appendChild(body);
|
|
3948
|
+
overlay.appendChild(legendWrapper);
|
|
3949
|
+
return () => {
|
|
3950
|
+
if (legendWrapper.parentNode === overlay) overlay.removeChild(legendWrapper);
|
|
3951
|
+
};
|
|
3916
3952
|
}
|
|
3917
3953
|
|
|
3918
3954
|
// src/utils/resolve-link-style.ts
|
|
@@ -4145,7 +4181,7 @@ function getLinkKey2(link) {
|
|
|
4145
4181
|
}
|
|
4146
4182
|
function renderLinkLabels(params, links) {
|
|
4147
4183
|
const renderableLinks = createRenderableLinks2(params, links);
|
|
4148
|
-
const labelSelection = params.root.select("
|
|
4184
|
+
const labelSelection = params.root.select('[data-layer="link-labels"]').selectAll(".link-label").data(renderableLinks, (item) => getLinkKey2(item.link)).join("g").attr("class", "link-label").attr("pointer-events", "auto").attr("cursor", "pointer");
|
|
4149
4185
|
labelSelection.selectAll("rect").data((item) => [item]).join("rect").attr("rx", (item) => item.style.label.borderRadius).attr("ry", (item) => item.style.label.borderRadius).attr("height", (item) => item.style.label.height).attr("fill", (item) => item.style.label.backgroundFill).attr("stroke", (item) => item.style.label.borderColor).attr("stroke-width", (item) => item.style.label.borderWidth);
|
|
4150
4186
|
labelSelection.selectAll("text").data((item) => [item]).join("text").attr("text-anchor", "middle").attr("dominant-baseline", "middle").attr("font-size", (item) => item.style.label.fontSize).attr("fill", (item) => item.style.label.textColor).text((item) => item.link.label ?? "");
|
|
4151
4187
|
return labelSelection;
|
|
@@ -4430,10 +4466,11 @@ function observeResize(element, onResize) {
|
|
|
4430
4466
|
if (!entry) {
|
|
4431
4467
|
return;
|
|
4432
4468
|
}
|
|
4433
|
-
|
|
4434
|
-
|
|
4435
|
-
|
|
4436
|
-
|
|
4469
|
+
const width = entry.borderBoxSize?.[0]?.inlineSize ?? entry.contentRect.width;
|
|
4470
|
+
const height = entry.borderBoxSize?.[0]?.blockSize ?? entry.contentRect.height;
|
|
4471
|
+
if (width > 0 && height > 0) {
|
|
4472
|
+
onResize(width, height);
|
|
4473
|
+
}
|
|
4437
4474
|
}
|
|
4438
4475
|
);
|
|
4439
4476
|
observer.observe(element);
|
|
@@ -4466,54 +4503,85 @@ function getLinkTargetPoint(link) {
|
|
|
4466
4503
|
};
|
|
4467
4504
|
}
|
|
4468
4505
|
|
|
4506
|
+
// src/utils/export-graph.ts
|
|
4507
|
+
import html2canvas from "html2canvas";
|
|
4508
|
+
async function captureAndDownloadGraph(container, options = {}) {
|
|
4509
|
+
const {
|
|
4510
|
+
fileName = `graph-export-${Date.now()}.png`,
|
|
4511
|
+
backgroundColor = "#ffffff",
|
|
4512
|
+
pixelRatio = 2
|
|
4513
|
+
} = options;
|
|
4514
|
+
const root2 = container.querySelector(".pg-root");
|
|
4515
|
+
if (!root2) return;
|
|
4516
|
+
const controls = root2.querySelector(".pg-controls");
|
|
4517
|
+
const legendToggle = root2.querySelector(".pg-legend-toggle");
|
|
4518
|
+
const interactionLayer = root2.querySelector(".pg-interaction-layer");
|
|
4519
|
+
const legend = root2.querySelector(".pg-legend");
|
|
4520
|
+
const wasCollapsed = legend?.classList.contains("pg-is-collapsed");
|
|
4521
|
+
if (controls) controls.style.display = "none";
|
|
4522
|
+
if (legendToggle) legendToggle.style.display = "none";
|
|
4523
|
+
if (interactionLayer) interactionLayer.style.display = "none";
|
|
4524
|
+
if (legend && wasCollapsed) {
|
|
4525
|
+
legend.classList.remove("pg-is-collapsed");
|
|
4526
|
+
}
|
|
4527
|
+
try {
|
|
4528
|
+
const canvas = await html2canvas(root2, {
|
|
4529
|
+
scale: pixelRatio,
|
|
4530
|
+
backgroundColor,
|
|
4531
|
+
useCORS: true,
|
|
4532
|
+
logging: false
|
|
4533
|
+
});
|
|
4534
|
+
const dataUrl = canvas.toDataURL("image/png");
|
|
4535
|
+
const link = document.createElement("a");
|
|
4536
|
+
link.download = fileName;
|
|
4537
|
+
link.href = dataUrl;
|
|
4538
|
+
link.click();
|
|
4539
|
+
} finally {
|
|
4540
|
+
if (controls) controls.style.display = "flex";
|
|
4541
|
+
if (legendToggle) legendToggle.style.display = "flex";
|
|
4542
|
+
if (interactionLayer) interactionLayer.style.display = "block";
|
|
4543
|
+
if (legend && wasCollapsed) {
|
|
4544
|
+
legend.classList.add("pg-is-collapsed");
|
|
4545
|
+
}
|
|
4546
|
+
}
|
|
4547
|
+
}
|
|
4548
|
+
|
|
4469
4549
|
// src/create-graph.ts
|
|
4470
4550
|
function createGraph(config) {
|
|
4471
4551
|
let cleanupResize = null;
|
|
4472
4552
|
let cleanupZoom = null;
|
|
4473
4553
|
let tooltipBinding = null;
|
|
4474
4554
|
let controls = null;
|
|
4555
|
+
let legendCleanup = null;
|
|
4475
4556
|
let dimensions = { width: 0, height: 0 };
|
|
4476
4557
|
let rootGroup = null;
|
|
4558
|
+
let svgElement = null;
|
|
4477
4559
|
let zoomBehavior = null;
|
|
4478
4560
|
let simulation = null;
|
|
4479
4561
|
function render() {
|
|
4480
4562
|
destroy();
|
|
4481
4563
|
const layers = createGraphLayers(config.container);
|
|
4564
|
+
svgElement = layers.svg;
|
|
4482
4565
|
rootGroup = layers.root;
|
|
4483
|
-
cleanupResize = observeResize(
|
|
4484
|
-
|
|
4485
|
-
(width,
|
|
4486
|
-
|
|
4487
|
-
|
|
4488
|
-
|
|
4489
|
-
|
|
4490
|
-
|
|
4491
|
-
|
|
4492
|
-
|
|
4493
|
-
);
|
|
4566
|
+
cleanupResize = observeResize(config.container, (width, height) => {
|
|
4567
|
+
dimensions = { width, height };
|
|
4568
|
+
layers.svg.setAttribute("width", String(width));
|
|
4569
|
+
layers.svg.setAttribute("height", String(height));
|
|
4570
|
+
layers.interactionRect.setAttribute("width", String(width));
|
|
4571
|
+
layers.interactionRect.setAttribute("height", String(height));
|
|
4572
|
+
simulation?.force("x", x_default2(width / 2).strength(0.03));
|
|
4573
|
+
simulation?.force("y", y_default2(height / 2).strength(0.03));
|
|
4574
|
+
simulation?.alpha(0.25).restart();
|
|
4575
|
+
});
|
|
4494
4576
|
const zoomResult = createZoom({
|
|
4495
|
-
|
|
4496
|
-
* D3 zoom must be attached to SVG
|
|
4497
|
-
* because it requires:
|
|
4498
|
-
*
|
|
4499
|
-
* width.baseVal
|
|
4500
|
-
* height.baseVal
|
|
4501
|
-
*/
|
|
4502
|
-
svg: config.container,
|
|
4503
|
-
/**
|
|
4504
|
-
* Used for pointer semantics /
|
|
4505
|
-
* pan filtering only
|
|
4506
|
-
*/
|
|
4577
|
+
svg: layers.svg,
|
|
4507
4578
|
interactionLayer: layers.interactionLayer,
|
|
4508
|
-
/**
|
|
4509
|
-
* Actual graph transform target
|
|
4510
|
-
*/
|
|
4511
4579
|
root: layers.root
|
|
4512
4580
|
});
|
|
4513
4581
|
zoomBehavior = zoomResult.behavior;
|
|
4514
4582
|
cleanupZoom = zoomResult.cleanup;
|
|
4515
4583
|
const root2 = select_default2(layers.root);
|
|
4516
|
-
const renderContext = { svg:
|
|
4584
|
+
const renderContext = { svg: layers.svg, root: root2, interaction: config.interaction };
|
|
4517
4585
|
const linkSelection = renderLinks(renderContext, config.links);
|
|
4518
4586
|
const linkLabelSelection = renderLinkLabels(renderContext, config.links);
|
|
4519
4587
|
const nodeSelection = renderNodes(renderContext, config.nodes);
|
|
@@ -4526,40 +4594,35 @@ function createGraph(config) {
|
|
|
4526
4594
|
};
|
|
4527
4595
|
const simulationResult = createGraphSimulation(simulationConfig);
|
|
4528
4596
|
simulation = simulationResult.simulation;
|
|
4529
|
-
simulation.on(
|
|
4530
|
-
"
|
|
4531
|
-
() => {
|
|
4532
|
-
|
|
4533
|
-
|
|
4534
|
-
|
|
4535
|
-
|
|
4536
|
-
|
|
4537
|
-
|
|
4538
|
-
|
|
4539
|
-
|
|
4540
|
-
|
|
4541
|
-
|
|
4542
|
-
|
|
4543
|
-
|
|
4544
|
-
|
|
4545
|
-
|
|
4546
|
-
|
|
4547
|
-
|
|
4548
|
-
|
|
4549
|
-
|
|
4550
|
-
|
|
4551
|
-
|
|
4552
|
-
|
|
4553
|
-
|
|
4554
|
-
nodeSelection.attr("cx", (d) => d.x ?? 0).attr("cy", (d) => d.y ?? 0);
|
|
4555
|
-
labelSelection.attr("x", (d) => d.x ?? 0).attr("y", (d) => d.y ?? 0);
|
|
4556
|
-
tooltipBinding?.reposition();
|
|
4557
|
-
}
|
|
4558
|
-
);
|
|
4597
|
+
simulation.on("tick", () => {
|
|
4598
|
+
linkSelection.attr("x1", (item) => item.link.source.x ?? 0).attr("y1", (item) => item.link.source.y ?? 0).attr("x2", (item) => getShortenedTargetPoint(item.link, item.style).x).attr("y2", (item) => getShortenedTargetPoint(item.link, item.style).y);
|
|
4599
|
+
linkLabelSelection.attr("transform", (item) => {
|
|
4600
|
+
const link = item.link;
|
|
4601
|
+
const source = link.source;
|
|
4602
|
+
const targetPoint = getLinkTargetPoint(link);
|
|
4603
|
+
const x3 = ((source.x ?? 0) + targetPoint.x) / 2;
|
|
4604
|
+
const y3 = ((source.y ?? 0) + targetPoint.y) / 2;
|
|
4605
|
+
return `translate(${x3}, ${y3})`;
|
|
4606
|
+
}).each(function() {
|
|
4607
|
+
const group = this;
|
|
4608
|
+
const text = group.querySelector("text");
|
|
4609
|
+
const rect = group.querySelector("rect");
|
|
4610
|
+
if (!text || !rect) return;
|
|
4611
|
+
const bBox = text.getBBox();
|
|
4612
|
+
const padding = 6;
|
|
4613
|
+
rect.setAttribute("x", String(bBox.x - padding));
|
|
4614
|
+
rect.setAttribute("y", String(bBox.y - padding));
|
|
4615
|
+
rect.setAttribute("width", String(bBox.width + padding * 2));
|
|
4616
|
+
rect.setAttribute("height", String(bBox.height + padding * 2));
|
|
4617
|
+
});
|
|
4618
|
+
nodeSelection.attr("cx", (d) => d.x ?? 0).attr("cy", (d) => d.y ?? 0);
|
|
4619
|
+
labelSelection.attr("x", (d) => d.x ?? 0).attr("y", (d) => d.y ?? 0);
|
|
4620
|
+
tooltipBinding?.reposition();
|
|
4621
|
+
});
|
|
4559
4622
|
if (config.interaction?.hover?.enabled) {
|
|
4560
4623
|
if (config.interaction?.hover?.tooltip?.enabled) {
|
|
4561
4624
|
tooltipBinding = bindNodeTooltip({
|
|
4562
|
-
container: config.container
|
|
4625
|
+
container: config.container,
|
|
4563
4626
|
selection: nodeSelection,
|
|
4564
4627
|
tooltipConfig: config.interaction.hover.tooltip
|
|
4565
4628
|
});
|
|
@@ -4569,64 +4632,47 @@ function createGraph(config) {
|
|
|
4569
4632
|
if (config.interaction?.drag?.enabled !== false) {
|
|
4570
4633
|
nodeSelection.call(createDragBehavior(simulation));
|
|
4571
4634
|
}
|
|
4572
|
-
if (config.interaction?.selection?.enabled) {
|
|
4573
|
-
}
|
|
4574
4635
|
if (config.controls?.enabled) {
|
|
4575
|
-
controls = createGraphControls(
|
|
4636
|
+
controls = createGraphControls(
|
|
4637
|
+
layers.overlay,
|
|
4638
|
+
{ zoomIn, zoomOut, resetView, fitView, destroy, render, exportGraph },
|
|
4639
|
+
config.controls
|
|
4640
|
+
);
|
|
4576
4641
|
controls.mount();
|
|
4577
4642
|
}
|
|
4643
|
+
if (config.legend?.enabled) {
|
|
4644
|
+
legendCleanup = createGraphLegend(layers.overlay, config.legend);
|
|
4645
|
+
}
|
|
4578
4646
|
}
|
|
4579
4647
|
function resetView() {
|
|
4580
|
-
if (!zoomBehavior)
|
|
4581
|
-
|
|
4582
|
-
}
|
|
4583
|
-
select_default2(config.container).transition().call(
|
|
4584
|
-
zoomBehavior.transform,
|
|
4585
|
-
identity2
|
|
4586
|
-
);
|
|
4648
|
+
if (!zoomBehavior || !svgElement) return;
|
|
4649
|
+
select_default2(svgElement).transition().call(zoomBehavior.transform, identity2);
|
|
4587
4650
|
}
|
|
4588
4651
|
function fitView() {
|
|
4589
|
-
if (!zoomBehavior || !rootGroup || dimensions.width === 0 || dimensions.height === 0)
|
|
4590
|
-
return;
|
|
4591
|
-
}
|
|
4652
|
+
if (!zoomBehavior || !rootGroup || !svgElement || dimensions.width === 0 || dimensions.height === 0) return;
|
|
4592
4653
|
const bounds = rootGroup.getBBox();
|
|
4593
|
-
if (bounds.width === 0 || bounds.height === 0)
|
|
4594
|
-
|
|
4595
|
-
|
|
4596
|
-
const
|
|
4597
|
-
const
|
|
4598
|
-
|
|
4599
|
-
width / bounds.width,
|
|
4600
|
-
height / bounds.height
|
|
4601
|
-
) * 0.9;
|
|
4602
|
-
const translateX = (width - bounds.width * scale) / 2 - bounds.x * scale;
|
|
4603
|
-
const translateY = (height - bounds.height * scale) / 2 - bounds.y * scale;
|
|
4604
|
-
const transform2 = identity2.translate(
|
|
4605
|
-
translateX,
|
|
4606
|
-
translateY
|
|
4607
|
-
).scale(scale);
|
|
4608
|
-
select_default2(config.container).transition().call(
|
|
4609
|
-
zoomBehavior.transform,
|
|
4610
|
-
transform2
|
|
4611
|
-
);
|
|
4654
|
+
if (bounds.width === 0 || bounds.height === 0) return;
|
|
4655
|
+
const scale = Math.min(dimensions.width / bounds.width, dimensions.height / bounds.height) * 0.9;
|
|
4656
|
+
const translateX = (dimensions.width - bounds.width * scale) / 2 - bounds.x * scale;
|
|
4657
|
+
const translateY = (dimensions.height - bounds.height * scale) / 2 - bounds.y * scale;
|
|
4658
|
+
const transform2 = identity2.translate(translateX, translateY).scale(scale);
|
|
4659
|
+
select_default2(svgElement).transition().call(zoomBehavior.transform, transform2);
|
|
4612
4660
|
}
|
|
4613
4661
|
function zoomIn() {
|
|
4614
|
-
if (!zoomBehavior)
|
|
4615
|
-
|
|
4616
|
-
}
|
|
4617
|
-
select_default2(config.container).transition().call(
|
|
4618
|
-
zoomBehavior.scaleBy,
|
|
4619
|
-
1.2
|
|
4620
|
-
);
|
|
4662
|
+
if (!zoomBehavior || !svgElement) return;
|
|
4663
|
+
select_default2(svgElement).transition().call(zoomBehavior.scaleBy, 1.2);
|
|
4621
4664
|
}
|
|
4622
4665
|
function zoomOut() {
|
|
4623
|
-
if (!zoomBehavior)
|
|
4624
|
-
|
|
4625
|
-
|
|
4626
|
-
|
|
4627
|
-
|
|
4628
|
-
|
|
4629
|
-
|
|
4666
|
+
if (!zoomBehavior || !svgElement) return;
|
|
4667
|
+
select_default2(svgElement).transition().call(zoomBehavior.scaleBy, 0.8);
|
|
4668
|
+
}
|
|
4669
|
+
async function exportGraph(fileName) {
|
|
4670
|
+
fitView();
|
|
4671
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
4672
|
+
await captureAndDownloadGraph(config.container, {
|
|
4673
|
+
fileName,
|
|
4674
|
+
pixelRatio: 2
|
|
4675
|
+
});
|
|
4630
4676
|
}
|
|
4631
4677
|
function destroy() {
|
|
4632
4678
|
if (cleanupResize) {
|
|
@@ -4649,13 +4695,18 @@ function createGraph(config) {
|
|
|
4649
4695
|
controls.destroy();
|
|
4650
4696
|
controls = null;
|
|
4651
4697
|
}
|
|
4698
|
+
if (legendCleanup) {
|
|
4699
|
+
legendCleanup();
|
|
4700
|
+
legendCleanup = null;
|
|
4701
|
+
}
|
|
4652
4702
|
rootGroup = null;
|
|
4703
|
+
svgElement = null;
|
|
4653
4704
|
zoomBehavior = null;
|
|
4654
4705
|
while (config.container.firstChild) {
|
|
4655
4706
|
config.container.removeChild(config.container.firstChild);
|
|
4656
4707
|
}
|
|
4657
4708
|
}
|
|
4658
|
-
return { render, zoomIn, zoomOut, resetView, fitView, destroy };
|
|
4709
|
+
return { render, zoomIn, zoomOut, resetView, fitView, destroy, exportGraph };
|
|
4659
4710
|
}
|
|
4660
4711
|
export {
|
|
4661
4712
|
createGraph
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "polly-graph",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "Reusable D3-based graph visualization SDK with configurable nodes, links, labels, interactions, and layout behaviors.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Badal",
|
|
@@ -55,7 +55,8 @@
|
|
|
55
55
|
"prepublishOnly": "npm run lint && npm run typecheck && npm run test && npm run build"
|
|
56
56
|
},
|
|
57
57
|
"dependencies": {
|
|
58
|
-
"d3": "7.9.0"
|
|
58
|
+
"d3": "7.9.0",
|
|
59
|
+
"html2canvas": "^1.4.1"
|
|
59
60
|
},
|
|
60
61
|
"devDependencies": {
|
|
61
62
|
"@eslint/js": "^9.39.4",
|