polly-graph 0.1.2 → 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 +331 -129
- package/dist/index.css +236 -0
- package/dist/index.d.cts +41 -1
- package/dist/index.d.ts +41 -1
- package/dist/index.js +321 -129
- package/package.json +5 -4
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
|
|
|
@@ -3783,6 +3786,171 @@ function createGraphSimulation(config) {
|
|
|
3783
3786
|
return { simulation };
|
|
3784
3787
|
}
|
|
3785
3788
|
|
|
3789
|
+
// src/controls/graph-controls.utils.ts
|
|
3790
|
+
function resolveControlsPosition(position) {
|
|
3791
|
+
return position ?? "bottom-left";
|
|
3792
|
+
}
|
|
3793
|
+
function resolveControlsOrientation(orientation) {
|
|
3794
|
+
return orientation ?? "vertical";
|
|
3795
|
+
}
|
|
3796
|
+
function shouldRenderControl(config, key) {
|
|
3797
|
+
const show = config.show;
|
|
3798
|
+
if (!show) {
|
|
3799
|
+
return true;
|
|
3800
|
+
}
|
|
3801
|
+
const value = show[key];
|
|
3802
|
+
if (value === void 0) {
|
|
3803
|
+
return true;
|
|
3804
|
+
}
|
|
3805
|
+
return value;
|
|
3806
|
+
}
|
|
3807
|
+
|
|
3808
|
+
// src/assets/plus.svg?raw
|
|
3809
|
+
var plus_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="M5 12h14m-7-7v14" />\n</svg>';
|
|
3810
|
+
|
|
3811
|
+
// src/assets/minus.svg?raw
|
|
3812
|
+
var minus_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="M19 12H5" />\n</svg>';
|
|
3813
|
+
|
|
3814
|
+
// src/assets/fit.svg?raw
|
|
3815
|
+
var fit_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="M5 9V5H9" />\n <path d="M19 9V5H15" />\n <path d="M5 15V19H9" />\n <path d="M19 15V19H15" />\n</svg>';
|
|
3816
|
+
|
|
3817
|
+
// src/assets/reset.svg?raw
|
|
3818
|
+
var reset_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="M20 12a8 8 0 1 1-2.3-5.7" />\n <path d="M20 4.5v4h-4" />\n</svg>';
|
|
3819
|
+
|
|
3820
|
+
// src/controls/graph-controls.icons.ts
|
|
3821
|
+
var ICON_MAP = {
|
|
3822
|
+
"zoom-in": plus_default,
|
|
3823
|
+
"zoom-out": minus_default,
|
|
3824
|
+
fit: fit_default,
|
|
3825
|
+
reset: reset_default
|
|
3826
|
+
};
|
|
3827
|
+
function getControlIcon(icon) {
|
|
3828
|
+
const raw = ICON_MAP[icon];
|
|
3829
|
+
if (!raw) {
|
|
3830
|
+
throw new Error(`Icon not found: ${icon}`);
|
|
3831
|
+
}
|
|
3832
|
+
return raw.replace("<svg", '<svg class="pg-icon"');
|
|
3833
|
+
}
|
|
3834
|
+
|
|
3835
|
+
// src/controls/create-graph-controls.ts
|
|
3836
|
+
function createGraphControls(overlay, graph, config) {
|
|
3837
|
+
let root2 = null;
|
|
3838
|
+
function mount() {
|
|
3839
|
+
if (!config.enabled) {
|
|
3840
|
+
return;
|
|
3841
|
+
}
|
|
3842
|
+
root2 = document.createElement("div");
|
|
3843
|
+
root2.className = "pg-controls";
|
|
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
|
+
}
|
|
3852
|
+
appendControls(root2, config, graph);
|
|
3853
|
+
overlay.appendChild(root2);
|
|
3854
|
+
}
|
|
3855
|
+
function appendControls(root3, config2, graph2) {
|
|
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
|
+
});
|
|
3867
|
+
}
|
|
3868
|
+
function createButton(type, label, onClick) {
|
|
3869
|
+
const button = document.createElement("button");
|
|
3870
|
+
button.className = "pg-control-btn";
|
|
3871
|
+
button.type = "button";
|
|
3872
|
+
button.setAttribute("aria-label", label);
|
|
3873
|
+
const wrapper = document.createElement("div");
|
|
3874
|
+
wrapper.className = "pg-icon-wrapper";
|
|
3875
|
+
wrapper.innerHTML = getControlIcon(type);
|
|
3876
|
+
const svg = wrapper.querySelector("svg");
|
|
3877
|
+
if (svg) {
|
|
3878
|
+
svg.classList.add("pg-icon");
|
|
3879
|
+
button.appendChild(svg);
|
|
3880
|
+
}
|
|
3881
|
+
button.addEventListener("click", onClick);
|
|
3882
|
+
return button;
|
|
3883
|
+
}
|
|
3884
|
+
function destroy() {
|
|
3885
|
+
if (!root2) {
|
|
3886
|
+
return;
|
|
3887
|
+
}
|
|
3888
|
+
if (root2.parentNode === overlay) {
|
|
3889
|
+
overlay.removeChild(root2);
|
|
3890
|
+
}
|
|
3891
|
+
root2 = null;
|
|
3892
|
+
}
|
|
3893
|
+
return { mount, destroy };
|
|
3894
|
+
}
|
|
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}`);
|
|
3905
|
+
}
|
|
3906
|
+
return raw.replace("<svg", '<svg class="pg-icon"');
|
|
3907
|
+
}
|
|
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
|
+
};
|
|
3952
|
+
}
|
|
3953
|
+
|
|
3786
3954
|
// src/utils/resolve-link-style.ts
|
|
3787
3955
|
var DEFAULT_LINK_STYLE = {
|
|
3788
3956
|
stroke: "#94a3b8",
|
|
@@ -4013,7 +4181,7 @@ function getLinkKey2(link) {
|
|
|
4013
4181
|
}
|
|
4014
4182
|
function renderLinkLabels(params, links) {
|
|
4015
4183
|
const renderableLinks = createRenderableLinks2(params, links);
|
|
4016
|
-
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");
|
|
4017
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);
|
|
4018
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 ?? "");
|
|
4019
4187
|
return labelSelection;
|
|
@@ -4298,10 +4466,11 @@ function observeResize(element, onResize) {
|
|
|
4298
4466
|
if (!entry) {
|
|
4299
4467
|
return;
|
|
4300
4468
|
}
|
|
4301
|
-
|
|
4302
|
-
|
|
4303
|
-
|
|
4304
|
-
|
|
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
|
+
}
|
|
4305
4474
|
}
|
|
4306
4475
|
);
|
|
4307
4476
|
observer.observe(element);
|
|
@@ -4334,53 +4503,85 @@ function getLinkTargetPoint(link) {
|
|
|
4334
4503
|
};
|
|
4335
4504
|
}
|
|
4336
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
|
+
|
|
4337
4549
|
// src/create-graph.ts
|
|
4338
4550
|
function createGraph(config) {
|
|
4339
4551
|
let cleanupResize = null;
|
|
4340
4552
|
let cleanupZoom = null;
|
|
4341
4553
|
let tooltipBinding = null;
|
|
4554
|
+
let controls = null;
|
|
4555
|
+
let legendCleanup = null;
|
|
4342
4556
|
let dimensions = { width: 0, height: 0 };
|
|
4343
4557
|
let rootGroup = null;
|
|
4558
|
+
let svgElement = null;
|
|
4344
4559
|
let zoomBehavior = null;
|
|
4345
4560
|
let simulation = null;
|
|
4346
4561
|
function render() {
|
|
4347
4562
|
destroy();
|
|
4348
4563
|
const layers = createGraphLayers(config.container);
|
|
4564
|
+
svgElement = layers.svg;
|
|
4349
4565
|
rootGroup = layers.root;
|
|
4350
|
-
cleanupResize = observeResize(
|
|
4351
|
-
|
|
4352
|
-
(width,
|
|
4353
|
-
|
|
4354
|
-
|
|
4355
|
-
|
|
4356
|
-
|
|
4357
|
-
|
|
4358
|
-
|
|
4359
|
-
|
|
4360
|
-
);
|
|
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
|
+
});
|
|
4361
4576
|
const zoomResult = createZoom({
|
|
4362
|
-
|
|
4363
|
-
* D3 zoom must be attached to SVG
|
|
4364
|
-
* because it requires:
|
|
4365
|
-
*
|
|
4366
|
-
* width.baseVal
|
|
4367
|
-
* height.baseVal
|
|
4368
|
-
*/
|
|
4369
|
-
svg: config.container,
|
|
4370
|
-
/**
|
|
4371
|
-
* Used for pointer semantics /
|
|
4372
|
-
* pan filtering only
|
|
4373
|
-
*/
|
|
4577
|
+
svg: layers.svg,
|
|
4374
4578
|
interactionLayer: layers.interactionLayer,
|
|
4375
|
-
/**
|
|
4376
|
-
* Actual graph transform target
|
|
4377
|
-
*/
|
|
4378
4579
|
root: layers.root
|
|
4379
4580
|
});
|
|
4380
4581
|
zoomBehavior = zoomResult.behavior;
|
|
4381
4582
|
cleanupZoom = zoomResult.cleanup;
|
|
4382
4583
|
const root2 = select_default2(layers.root);
|
|
4383
|
-
const renderContext = { svg:
|
|
4584
|
+
const renderContext = { svg: layers.svg, root: root2, interaction: config.interaction };
|
|
4384
4585
|
const linkSelection = renderLinks(renderContext, config.links);
|
|
4385
4586
|
const linkLabelSelection = renderLinkLabels(renderContext, config.links);
|
|
4386
4587
|
const nodeSelection = renderNodes(renderContext, config.nodes);
|
|
@@ -4393,40 +4594,35 @@ function createGraph(config) {
|
|
|
4393
4594
|
};
|
|
4394
4595
|
const simulationResult = createGraphSimulation(simulationConfig);
|
|
4395
4596
|
simulation = simulationResult.simulation;
|
|
4396
|
-
simulation.on(
|
|
4397
|
-
"
|
|
4398
|
-
() => {
|
|
4399
|
-
|
|
4400
|
-
|
|
4401
|
-
|
|
4402
|
-
|
|
4403
|
-
|
|
4404
|
-
|
|
4405
|
-
|
|
4406
|
-
|
|
4407
|
-
|
|
4408
|
-
|
|
4409
|
-
|
|
4410
|
-
|
|
4411
|
-
|
|
4412
|
-
|
|
4413
|
-
|
|
4414
|
-
|
|
4415
|
-
|
|
4416
|
-
|
|
4417
|
-
|
|
4418
|
-
|
|
4419
|
-
|
|
4420
|
-
|
|
4421
|
-
nodeSelection.attr("cx", (d) => d.x ?? 0).attr("cy", (d) => d.y ?? 0);
|
|
4422
|
-
labelSelection.attr("x", (d) => d.x ?? 0).attr("y", (d) => d.y ?? 0);
|
|
4423
|
-
tooltipBinding?.reposition();
|
|
4424
|
-
}
|
|
4425
|
-
);
|
|
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
|
+
});
|
|
4426
4622
|
if (config.interaction?.hover?.enabled) {
|
|
4427
4623
|
if (config.interaction?.hover?.tooltip?.enabled) {
|
|
4428
4624
|
tooltipBinding = bindNodeTooltip({
|
|
4429
|
-
container: config.container
|
|
4625
|
+
container: config.container,
|
|
4430
4626
|
selection: nodeSelection,
|
|
4431
4627
|
tooltipConfig: config.interaction.hover.tooltip
|
|
4432
4628
|
});
|
|
@@ -4436,60 +4632,47 @@ function createGraph(config) {
|
|
|
4436
4632
|
if (config.interaction?.drag?.enabled !== false) {
|
|
4437
4633
|
nodeSelection.call(createDragBehavior(simulation));
|
|
4438
4634
|
}
|
|
4439
|
-
if (config.
|
|
4635
|
+
if (config.controls?.enabled) {
|
|
4636
|
+
controls = createGraphControls(
|
|
4637
|
+
layers.overlay,
|
|
4638
|
+
{ zoomIn, zoomOut, resetView, fitView, destroy, render, exportGraph },
|
|
4639
|
+
config.controls
|
|
4640
|
+
);
|
|
4641
|
+
controls.mount();
|
|
4642
|
+
}
|
|
4643
|
+
if (config.legend?.enabled) {
|
|
4644
|
+
legendCleanup = createGraphLegend(layers.overlay, config.legend);
|
|
4440
4645
|
}
|
|
4441
4646
|
}
|
|
4442
4647
|
function resetView() {
|
|
4443
|
-
if (!zoomBehavior)
|
|
4444
|
-
|
|
4445
|
-
}
|
|
4446
|
-
select_default2(config.container).transition().call(
|
|
4447
|
-
zoomBehavior.transform,
|
|
4448
|
-
identity2
|
|
4449
|
-
);
|
|
4648
|
+
if (!zoomBehavior || !svgElement) return;
|
|
4649
|
+
select_default2(svgElement).transition().call(zoomBehavior.transform, identity2);
|
|
4450
4650
|
}
|
|
4451
4651
|
function fitView() {
|
|
4452
|
-
if (!zoomBehavior || !rootGroup || dimensions.width === 0 || dimensions.height === 0)
|
|
4453
|
-
return;
|
|
4454
|
-
}
|
|
4652
|
+
if (!zoomBehavior || !rootGroup || !svgElement || dimensions.width === 0 || dimensions.height === 0) return;
|
|
4455
4653
|
const bounds = rootGroup.getBBox();
|
|
4456
|
-
if (bounds.width === 0 || bounds.height === 0)
|
|
4457
|
-
|
|
4458
|
-
|
|
4459
|
-
const
|
|
4460
|
-
const
|
|
4461
|
-
|
|
4462
|
-
width / bounds.width,
|
|
4463
|
-
height / bounds.height
|
|
4464
|
-
) * 0.9;
|
|
4465
|
-
const translateX = (width - bounds.width * scale) / 2 - bounds.x * scale;
|
|
4466
|
-
const translateY = (height - bounds.height * scale) / 2 - bounds.y * scale;
|
|
4467
|
-
const transform2 = identity2.translate(
|
|
4468
|
-
translateX,
|
|
4469
|
-
translateY
|
|
4470
|
-
).scale(scale);
|
|
4471
|
-
select_default2(config.container).transition().call(
|
|
4472
|
-
zoomBehavior.transform,
|
|
4473
|
-
transform2
|
|
4474
|
-
);
|
|
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);
|
|
4475
4660
|
}
|
|
4476
4661
|
function zoomIn() {
|
|
4477
|
-
if (!zoomBehavior)
|
|
4478
|
-
|
|
4479
|
-
}
|
|
4480
|
-
select_default2(config.container).transition().call(
|
|
4481
|
-
zoomBehavior.scaleBy,
|
|
4482
|
-
1.2
|
|
4483
|
-
);
|
|
4662
|
+
if (!zoomBehavior || !svgElement) return;
|
|
4663
|
+
select_default2(svgElement).transition().call(zoomBehavior.scaleBy, 1.2);
|
|
4484
4664
|
}
|
|
4485
4665
|
function zoomOut() {
|
|
4486
|
-
if (!zoomBehavior)
|
|
4487
|
-
|
|
4488
|
-
|
|
4489
|
-
|
|
4490
|
-
|
|
4491
|
-
|
|
4492
|
-
|
|
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
|
+
});
|
|
4493
4676
|
}
|
|
4494
4677
|
function destroy() {
|
|
4495
4678
|
if (cleanupResize) {
|
|
@@ -4508,13 +4691,22 @@ function createGraph(config) {
|
|
|
4508
4691
|
simulation.stop();
|
|
4509
4692
|
simulation = null;
|
|
4510
4693
|
}
|
|
4694
|
+
if (controls) {
|
|
4695
|
+
controls.destroy();
|
|
4696
|
+
controls = null;
|
|
4697
|
+
}
|
|
4698
|
+
if (legendCleanup) {
|
|
4699
|
+
legendCleanup();
|
|
4700
|
+
legendCleanup = null;
|
|
4701
|
+
}
|
|
4511
4702
|
rootGroup = null;
|
|
4703
|
+
svgElement = null;
|
|
4512
4704
|
zoomBehavior = null;
|
|
4513
4705
|
while (config.container.firstChild) {
|
|
4514
4706
|
config.container.removeChild(config.container.firstChild);
|
|
4515
4707
|
}
|
|
4516
4708
|
}
|
|
4517
|
-
return { render, zoomIn, zoomOut, resetView, fitView, destroy };
|
|
4709
|
+
return { render, zoomIn, zoomOut, resetView, fitView, destroy, exportGraph };
|
|
4518
4710
|
}
|
|
4519
4711
|
export {
|
|
4520
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",
|
|
@@ -47,15 +47,16 @@
|
|
|
47
47
|
},
|
|
48
48
|
"scripts": {
|
|
49
49
|
"clean": "rm -rf dist",
|
|
50
|
-
"build": "tsup
|
|
51
|
-
"dev": "tsup
|
|
50
|
+
"build": "tsup",
|
|
51
|
+
"dev": "tsup",
|
|
52
52
|
"lint": "eslint \"src/**/*.ts\"",
|
|
53
53
|
"typecheck": "tsc --noEmit",
|
|
54
54
|
"test": "vitest run --passWithNoTests",
|
|
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",
|