polly-graph 0.1.3 → 0.1.5
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/README.md +82 -159
- package/dist/index.cjs +314 -299
- package/dist/index.css +221 -32
- package/dist/index.d.cts +30 -3
- package/dist/index.d.ts +30 -3
- package/dist/index.js +304 -299
- package/package.json +3 -2
package/dist/index.cjs
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
2
3
|
var __defProp = Object.defineProperty;
|
|
3
4
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
5
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
8
|
var __export = (target, all) => {
|
|
7
9
|
for (var name in all)
|
|
@@ -15,6 +17,14 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
15
17
|
}
|
|
16
18
|
return to;
|
|
17
19
|
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
18
28
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
29
|
|
|
20
30
|
// src/index.ts
|
|
@@ -3634,104 +3644,45 @@ function manyBody_default() {
|
|
|
3634
3644
|
return force;
|
|
3635
3645
|
}
|
|
3636
3646
|
|
|
3637
|
-
// node_modules/d3-force/src/x.js
|
|
3638
|
-
function x_default2(x3) {
|
|
3639
|
-
var strength = constant_default5(0.1), nodes, strengths, xz;
|
|
3640
|
-
if (typeof x3 !== "function") x3 = constant_default5(x3 == null ? 0 : +x3);
|
|
3641
|
-
function force(alpha) {
|
|
3642
|
-
for (var i = 0, n = nodes.length, node; i < n; ++i) {
|
|
3643
|
-
node = nodes[i], node.vx += (xz[i] - node.x) * strengths[i] * alpha;
|
|
3644
|
-
}
|
|
3645
|
-
}
|
|
3646
|
-
function initialize() {
|
|
3647
|
-
if (!nodes) return;
|
|
3648
|
-
var i, n = nodes.length;
|
|
3649
|
-
strengths = new Array(n);
|
|
3650
|
-
xz = new Array(n);
|
|
3651
|
-
for (i = 0; i < n; ++i) {
|
|
3652
|
-
strengths[i] = isNaN(xz[i] = +x3(nodes[i], i, nodes)) ? 0 : +strength(nodes[i], i, nodes);
|
|
3653
|
-
}
|
|
3654
|
-
}
|
|
3655
|
-
force.initialize = function(_) {
|
|
3656
|
-
nodes = _;
|
|
3657
|
-
initialize();
|
|
3658
|
-
};
|
|
3659
|
-
force.strength = function(_) {
|
|
3660
|
-
return arguments.length ? (strength = typeof _ === "function" ? _ : constant_default5(+_), initialize(), force) : strength;
|
|
3661
|
-
};
|
|
3662
|
-
force.x = function(_) {
|
|
3663
|
-
return arguments.length ? (x3 = typeof _ === "function" ? _ : constant_default5(+_), initialize(), force) : x3;
|
|
3664
|
-
};
|
|
3665
|
-
return force;
|
|
3666
|
-
}
|
|
3667
|
-
|
|
3668
|
-
// node_modules/d3-force/src/y.js
|
|
3669
|
-
function y_default2(y3) {
|
|
3670
|
-
var strength = constant_default5(0.1), nodes, strengths, yz;
|
|
3671
|
-
if (typeof y3 !== "function") y3 = constant_default5(y3 == null ? 0 : +y3);
|
|
3672
|
-
function force(alpha) {
|
|
3673
|
-
for (var i = 0, n = nodes.length, node; i < n; ++i) {
|
|
3674
|
-
node = nodes[i], node.vy += (yz[i] - node.y) * strengths[i] * alpha;
|
|
3675
|
-
}
|
|
3676
|
-
}
|
|
3677
|
-
function initialize() {
|
|
3678
|
-
if (!nodes) return;
|
|
3679
|
-
var i, n = nodes.length;
|
|
3680
|
-
strengths = new Array(n);
|
|
3681
|
-
yz = new Array(n);
|
|
3682
|
-
for (i = 0; i < n; ++i) {
|
|
3683
|
-
strengths[i] = isNaN(yz[i] = +y3(nodes[i], i, nodes)) ? 0 : +strength(nodes[i], i, nodes);
|
|
3684
|
-
}
|
|
3685
|
-
}
|
|
3686
|
-
force.initialize = function(_) {
|
|
3687
|
-
nodes = _;
|
|
3688
|
-
initialize();
|
|
3689
|
-
};
|
|
3690
|
-
force.strength = function(_) {
|
|
3691
|
-
return arguments.length ? (strength = typeof _ === "function" ? _ : constant_default5(+_), initialize(), force) : strength;
|
|
3692
|
-
};
|
|
3693
|
-
force.y = function(_) {
|
|
3694
|
-
return arguments.length ? (y3 = typeof _ === "function" ? _ : constant_default5(+_), initialize(), force) : y3;
|
|
3695
|
-
};
|
|
3696
|
-
return force;
|
|
3697
|
-
}
|
|
3698
|
-
|
|
3699
3647
|
// src/core/create-graph-layers.ts
|
|
3700
|
-
function createGraphLayers(
|
|
3701
|
-
|
|
3702
|
-
|
|
3703
|
-
|
|
3704
|
-
const
|
|
3648
|
+
function createGraphLayers(host) {
|
|
3649
|
+
host.innerHTML = "";
|
|
3650
|
+
const rootContainer = document.createElement("div");
|
|
3651
|
+
rootContainer.className = "pg-root";
|
|
3652
|
+
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
3653
|
+
svg.setAttribute("class", "pg-canvas");
|
|
3654
|
+
const overlay = document.createElement("div");
|
|
3655
|
+
overlay.className = "pg-overlay";
|
|
3656
|
+
rootContainer.appendChild(svg);
|
|
3657
|
+
rootContainer.appendChild(overlay);
|
|
3658
|
+
host.appendChild(rootContainer);
|
|
3659
|
+
const createGroup = (layerName) => {
|
|
3705
3660
|
const group = document.createElementNS("http://www.w3.org/2000/svg", "g");
|
|
3706
|
-
group.setAttribute("class",
|
|
3707
|
-
group.setAttribute("data-layer",
|
|
3661
|
+
group.setAttribute("class", `pg-layer-${layerName}`);
|
|
3662
|
+
group.setAttribute("data-layer", layerName);
|
|
3708
3663
|
return group;
|
|
3709
3664
|
};
|
|
3710
3665
|
const interactionLayer = createGroup("interaction-layer");
|
|
3711
3666
|
const interactionRect = document.createElementNS("http://www.w3.org/2000/svg", "rect");
|
|
3712
|
-
|
|
3713
|
-
|
|
3714
|
-
|
|
3715
|
-
height: "100%",
|
|
3716
|
-
fill: "transparent",
|
|
3717
|
-
"pointer-events": "all"
|
|
3718
|
-
};
|
|
3719
|
-
Object.entries(interactionAttributes).forEach(([key, value]) => {
|
|
3720
|
-
interactionRect.setAttribute(key, value);
|
|
3721
|
-
});
|
|
3667
|
+
interactionRect.setAttribute("class", "pg-interaction-surface");
|
|
3668
|
+
interactionRect.setAttribute("fill", "transparent");
|
|
3669
|
+
interactionRect.setAttribute("pointer-events", "all");
|
|
3722
3670
|
interactionLayer.appendChild(interactionRect);
|
|
3723
|
-
const
|
|
3671
|
+
const graphRoot = createGroup("viewport");
|
|
3724
3672
|
const layers = {
|
|
3673
|
+
svg,
|
|
3674
|
+
overlay,
|
|
3725
3675
|
interactionLayer,
|
|
3726
3676
|
interactionRect,
|
|
3727
|
-
root:
|
|
3677
|
+
root: graphRoot,
|
|
3678
|
+
// These keys now match your ctx.root.select('[data-layer="..."]') calls
|
|
3728
3679
|
links: createGroup("links"),
|
|
3729
3680
|
linkLabels: createGroup("link-labels"),
|
|
3730
3681
|
nodeRings: createGroup("node-rings"),
|
|
3731
3682
|
nodes: createGroup("nodes"),
|
|
3732
3683
|
nodeLabels: createGroup("node-labels")
|
|
3733
3684
|
};
|
|
3734
|
-
|
|
3685
|
+
graphRoot.append(
|
|
3735
3686
|
layers.links,
|
|
3736
3687
|
layers.linkLabels,
|
|
3737
3688
|
layers.nodeRings,
|
|
@@ -3739,7 +3690,7 @@ function createGraphLayers(svg) {
|
|
|
3739
3690
|
layers.nodeLabels
|
|
3740
3691
|
);
|
|
3741
3692
|
svg.appendChild(interactionLayer);
|
|
3742
|
-
svg.appendChild(
|
|
3693
|
+
svg.appendChild(graphRoot);
|
|
3743
3694
|
return layers;
|
|
3744
3695
|
}
|
|
3745
3696
|
|
|
@@ -3801,10 +3752,17 @@ function createGraphSimulation(config) {
|
|
|
3801
3752
|
});
|
|
3802
3753
|
const simulation = simulation_default(config.nodes).alpha(0.9).alphaDecay(0.12).alphaMin(0.03).velocityDecay(0.5).force(
|
|
3803
3754
|
"link",
|
|
3804
|
-
link_default(config.links).id((d) => d.id).distance(
|
|
3755
|
+
link_default(config.links).id((d) => d.id).distance((d) => {
|
|
3756
|
+
const source = d.source;
|
|
3757
|
+
const target = d.target;
|
|
3758
|
+
const sourceR = source.style?.radius || 20;
|
|
3759
|
+
const targetR = target.style?.radius || 20;
|
|
3760
|
+
const labelBuffer = d.style?.label?.height || 40;
|
|
3761
|
+
return (sourceR + targetR + labelBuffer) * 2;
|
|
3762
|
+
}).strength(0.8)
|
|
3805
3763
|
).force("charge", manyBody_default().strength(-220)).force(
|
|
3806
3764
|
"collide",
|
|
3807
|
-
collide_default().radius((node) => (node.style?.radius ?? 12) + 10).
|
|
3765
|
+
collide_default().radius((node) => (node.style?.radius ?? 12) + 10).iterations(2)
|
|
3808
3766
|
).force("center", center_default(centerX, centerY).strength(0.08));
|
|
3809
3767
|
return { simulation };
|
|
3810
3768
|
}
|
|
@@ -3856,49 +3814,51 @@ function getControlIcon(icon) {
|
|
|
3856
3814
|
}
|
|
3857
3815
|
|
|
3858
3816
|
// src/controls/create-graph-controls.ts
|
|
3859
|
-
function createGraphControls(
|
|
3817
|
+
function createGraphControls(overlay, graph, config) {
|
|
3860
3818
|
let root2 = null;
|
|
3861
3819
|
function mount() {
|
|
3862
3820
|
if (!config.enabled) {
|
|
3863
3821
|
return;
|
|
3864
3822
|
}
|
|
3865
|
-
const parent = container.parentElement;
|
|
3866
|
-
if (!parent) {
|
|
3867
|
-
return;
|
|
3868
|
-
}
|
|
3869
3823
|
root2 = document.createElement("div");
|
|
3870
3824
|
root2.className = "pg-controls";
|
|
3871
|
-
|
|
3872
|
-
|
|
3825
|
+
const position = resolveControlsPosition(config.position);
|
|
3826
|
+
root2.classList.add(`pg-pos-${position}`);
|
|
3827
|
+
const orientation = resolveControlsOrientation(config.orientation);
|
|
3828
|
+
root2.classList.add(`pg-orient-${orientation}`);
|
|
3829
|
+
if (config.offset) {
|
|
3830
|
+
root2.style.setProperty("--pg-controls-offset-x", `${config.offset.x}px`);
|
|
3831
|
+
root2.style.setProperty("--pg-controls-offset-y", `${config.offset.y}px`);
|
|
3832
|
+
}
|
|
3873
3833
|
appendControls(root2, config, graph);
|
|
3874
|
-
|
|
3834
|
+
overlay.appendChild(root2);
|
|
3875
3835
|
}
|
|
3876
3836
|
function appendControls(root3, config2, graph2) {
|
|
3877
|
-
|
|
3878
|
-
|
|
3879
|
-
|
|
3880
|
-
|
|
3881
|
-
|
|
3882
|
-
|
|
3883
|
-
|
|
3884
|
-
|
|
3885
|
-
|
|
3886
|
-
|
|
3887
|
-
|
|
3888
|
-
}
|
|
3837
|
+
const actions = [
|
|
3838
|
+
{ key: "zoomIn", icon: "zoom-in", label: "Zoom in", fn: graph2.zoomIn.bind(graph2) },
|
|
3839
|
+
{ key: "zoomOut", icon: "zoom-out", label: "Zoom out", fn: graph2.zoomOut.bind(graph2) },
|
|
3840
|
+
{ key: "fit", icon: "fit", label: "Fit view", fn: graph2.fitView.bind(graph2) },
|
|
3841
|
+
{ key: "reset", icon: "reset", label: "Reset view", fn: graph2.resetView.bind(graph2) }
|
|
3842
|
+
];
|
|
3843
|
+
actions.forEach((action) => {
|
|
3844
|
+
if (shouldRenderControl(config2, action.key)) {
|
|
3845
|
+
root3.appendChild(createButton(action.icon, action.label, action.fn));
|
|
3846
|
+
}
|
|
3847
|
+
});
|
|
3889
3848
|
}
|
|
3890
3849
|
function createButton(type, label, onClick) {
|
|
3891
3850
|
const button = document.createElement("button");
|
|
3851
|
+
button.className = "pg-control-btn";
|
|
3892
3852
|
button.type = "button";
|
|
3893
3853
|
button.setAttribute("aria-label", label);
|
|
3894
3854
|
const wrapper = document.createElement("div");
|
|
3855
|
+
wrapper.className = "pg-icon-wrapper";
|
|
3895
3856
|
wrapper.innerHTML = getControlIcon(type);
|
|
3896
3857
|
const svg = wrapper.querySelector("svg");
|
|
3897
|
-
if (
|
|
3898
|
-
|
|
3858
|
+
if (svg) {
|
|
3859
|
+
svg.classList.add("pg-icon");
|
|
3860
|
+
button.appendChild(svg);
|
|
3899
3861
|
}
|
|
3900
|
-
svg.classList.add("pg-icon");
|
|
3901
|
-
button.appendChild(svg);
|
|
3902
3862
|
button.addEventListener("click", onClick);
|
|
3903
3863
|
return button;
|
|
3904
3864
|
}
|
|
@@ -3906,39 +3866,70 @@ function createGraphControls(container, graph, config) {
|
|
|
3906
3866
|
if (!root2) {
|
|
3907
3867
|
return;
|
|
3908
3868
|
}
|
|
3909
|
-
|
|
3910
|
-
|
|
3869
|
+
if (root2.parentNode === overlay) {
|
|
3870
|
+
overlay.removeChild(root2);
|
|
3871
|
+
}
|
|
3911
3872
|
root2 = null;
|
|
3912
3873
|
}
|
|
3913
3874
|
return { mount, destroy };
|
|
3914
3875
|
}
|
|
3915
|
-
|
|
3916
|
-
|
|
3917
|
-
|
|
3918
|
-
|
|
3919
|
-
|
|
3920
|
-
|
|
3921
|
-
|
|
3922
|
-
|
|
3923
|
-
|
|
3924
|
-
|
|
3925
|
-
el.style.right = `${offset.x}px`;
|
|
3926
|
-
el.style.bottom = `${offset.y}px`;
|
|
3927
|
-
break;
|
|
3928
|
-
case "top-left":
|
|
3929
|
-
el.style.left = `${offset.x}px`;
|
|
3930
|
-
el.style.top = `${offset.y}px`;
|
|
3931
|
-
break;
|
|
3932
|
-
case "top-right":
|
|
3933
|
-
el.style.right = `${offset.x}px`;
|
|
3934
|
-
el.style.top = `${offset.y}px`;
|
|
3935
|
-
break;
|
|
3876
|
+
|
|
3877
|
+
// src/assets/caret.svg?raw
|
|
3878
|
+
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>';
|
|
3879
|
+
|
|
3880
|
+
// src/legends/graph-legend-icon.ts
|
|
3881
|
+
var LEGEND_ICON_MAP = { caret: caret_default };
|
|
3882
|
+
function getLegendIcon(icon) {
|
|
3883
|
+
const raw = LEGEND_ICON_MAP[icon];
|
|
3884
|
+
if (!raw) {
|
|
3885
|
+
throw new Error(`Legend icon not found: ${icon}`);
|
|
3936
3886
|
}
|
|
3887
|
+
return raw.replace("<svg", '<svg class="pg-icon"');
|
|
3937
3888
|
}
|
|
3938
|
-
|
|
3939
|
-
|
|
3940
|
-
|
|
3941
|
-
|
|
3889
|
+
|
|
3890
|
+
// src/legends/create-graph-legends.ts
|
|
3891
|
+
function createGraphLegend(overlay, config) {
|
|
3892
|
+
const legendWrapper = document.createElement("div");
|
|
3893
|
+
legendWrapper.className = "pg-legend";
|
|
3894
|
+
const position = config.position || "bottom-right";
|
|
3895
|
+
legendWrapper.classList.add(`pg-pos-${position}`);
|
|
3896
|
+
if (config.defaultExpanded === false) {
|
|
3897
|
+
legendWrapper.classList.add("pg-is-collapsed");
|
|
3898
|
+
}
|
|
3899
|
+
if (config.collapsible) {
|
|
3900
|
+
const toggleBtn = document.createElement("button");
|
|
3901
|
+
toggleBtn.className = "pg-legend-toggle";
|
|
3902
|
+
toggleBtn.type = "button";
|
|
3903
|
+
toggleBtn.innerHTML = getLegendIcon("caret");
|
|
3904
|
+
toggleBtn.onclick = (e) => {
|
|
3905
|
+
e.stopPropagation();
|
|
3906
|
+
legendWrapper.classList.toggle("pg-is-collapsed");
|
|
3907
|
+
};
|
|
3908
|
+
legendWrapper.appendChild(toggleBtn);
|
|
3909
|
+
}
|
|
3910
|
+
const body = document.createElement("div");
|
|
3911
|
+
body.className = "pg-legend-body";
|
|
3912
|
+
const list = document.createElement("ul");
|
|
3913
|
+
list.className = "pg-legend-list";
|
|
3914
|
+
config.items.forEach((item) => {
|
|
3915
|
+
const listItem = document.createElement("li");
|
|
3916
|
+
listItem.className = "pg-legend-item";
|
|
3917
|
+
const swatch = document.createElement("span");
|
|
3918
|
+
swatch.className = `pg-legend-swatch is-${item.shape || "circle"}`;
|
|
3919
|
+
swatch.style.backgroundColor = item.color;
|
|
3920
|
+
const label = document.createElement("span");
|
|
3921
|
+
label.className = "pg-legend-label";
|
|
3922
|
+
label.innerText = item.label;
|
|
3923
|
+
listItem.appendChild(swatch);
|
|
3924
|
+
listItem.appendChild(label);
|
|
3925
|
+
list.appendChild(listItem);
|
|
3926
|
+
});
|
|
3927
|
+
body.appendChild(list);
|
|
3928
|
+
legendWrapper.appendChild(body);
|
|
3929
|
+
overlay.appendChild(legendWrapper);
|
|
3930
|
+
return () => {
|
|
3931
|
+
if (legendWrapper.parentNode === overlay) overlay.removeChild(legendWrapper);
|
|
3932
|
+
};
|
|
3942
3933
|
}
|
|
3943
3934
|
|
|
3944
3935
|
// src/utils/resolve-link-style.ts
|
|
@@ -3954,6 +3945,7 @@ var DEFAULT_LINK_STYLE = {
|
|
|
3954
3945
|
},
|
|
3955
3946
|
label: {
|
|
3956
3947
|
enabled: true,
|
|
3948
|
+
visibility: "always",
|
|
3957
3949
|
backgroundFill: "color-mix(in srgb, #8E42EE, #FFFFFF 90%)",
|
|
3958
3950
|
borderColor: "color-mix(in srgb, #8E42EE, #FFFFFF 10%)",
|
|
3959
3951
|
borderWidth: 1.5,
|
|
@@ -3989,6 +3981,7 @@ function mergeLinkStyle(base, override) {
|
|
|
3989
3981
|
},
|
|
3990
3982
|
label: {
|
|
3991
3983
|
enabled: override?.label?.enabled ?? base.label.enabled,
|
|
3984
|
+
visibility: override?.label?.visibility ?? base.label.visibility,
|
|
3992
3985
|
backgroundFill: override?.label?.backgroundFill ?? base.label.backgroundFill,
|
|
3993
3986
|
borderColor: override?.label?.borderColor ?? base.label.borderColor,
|
|
3994
3987
|
borderWidth: override?.label?.borderWidth ?? base.label.borderWidth,
|
|
@@ -4089,7 +4082,14 @@ function getLinkKey(link) {
|
|
|
4089
4082
|
}
|
|
4090
4083
|
function renderLinks(ctx, links) {
|
|
4091
4084
|
const renderableLinks = createRenderableLinks(ctx, links);
|
|
4092
|
-
|
|
4085
|
+
const linkSelection = ctx.root.select('[data-layer="links"]').selectAll("line").data(renderableLinks, (item) => getLinkKey(item.link)).join("line").attr("class", "graph-link").attr("stroke", (item) => item.style.stroke).attr("stroke-width", (item) => item.style.strokeWidth).attr("opacity", (item) => item.style.opacity).attr("marker-end", (item) => item.markerEnd).style("pointer-events", "stroke");
|
|
4086
|
+
const labelSelection = ctx.root.selectAll(".link-label");
|
|
4087
|
+
linkSelection.on("mouseenter.label-hover", (_event, d) => {
|
|
4088
|
+
labelSelection.filter((labelItem) => labelItem.link === d.link && labelItem.style.label.visibility === "hover").interrupt().transition().duration(200).style("opacity", 1);
|
|
4089
|
+
}).on("mouseleave.label-hover", (_event, d) => {
|
|
4090
|
+
labelSelection.filter((labelItem) => labelItem.link === d.link && labelItem.style.label.visibility === "hover").interrupt().transition().duration(200).style("opacity", 0);
|
|
4091
|
+
});
|
|
4092
|
+
return linkSelection;
|
|
4093
4093
|
}
|
|
4094
4094
|
|
|
4095
4095
|
// src/renderer/nodes.ts
|
|
@@ -4159,7 +4159,10 @@ function renderNodeLabels(ctx, nodes) {
|
|
|
4159
4159
|
// src/renderer/link-labels.ts
|
|
4160
4160
|
function createRenderableLinks2(params, links) {
|
|
4161
4161
|
return links.map(
|
|
4162
|
-
(link) => ({
|
|
4162
|
+
(link) => ({
|
|
4163
|
+
link,
|
|
4164
|
+
style: resolveLinkStyle({ link, interaction: params.interaction })
|
|
4165
|
+
})
|
|
4163
4166
|
).filter(
|
|
4164
4167
|
(item) => item.style.label.enabled && Boolean(item.link.label)
|
|
4165
4168
|
);
|
|
@@ -4171,7 +4174,13 @@ function getLinkKey2(link) {
|
|
|
4171
4174
|
}
|
|
4172
4175
|
function renderLinkLabels(params, links) {
|
|
4173
4176
|
const renderableLinks = createRenderableLinks2(params, links);
|
|
4174
|
-
const labelSelection = params.root.select("
|
|
4177
|
+
const labelSelection = params.root.select('[data-layer="link-labels"]').selectAll(".link-label").data(renderableLinks, (item) => getLinkKey2(item.link)).join("g").attr("class", "link-label").style("opacity", (item) => {
|
|
4178
|
+
const visibility = item.style.label.visibility ?? "always";
|
|
4179
|
+
return visibility === "always" ? 1 : 0;
|
|
4180
|
+
}).style("pointer-events", (item) => {
|
|
4181
|
+
const visibility = item.style.label.visibility ?? "always";
|
|
4182
|
+
return visibility === "always" ? "auto" : "none";
|
|
4183
|
+
}).style("cursor", "pointer");
|
|
4175
4184
|
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);
|
|
4176
4185
|
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 ?? "");
|
|
4177
4186
|
return labelSelection;
|
|
@@ -4200,58 +4209,35 @@ function createDragBehavior(simulation) {
|
|
|
4200
4209
|
|
|
4201
4210
|
// src/interactions/create-node-hover.ts
|
|
4202
4211
|
function createNodeHover(nodeSelection, hoverStyle) {
|
|
4203
|
-
|
|
4204
|
-
|
|
4205
|
-
|
|
4206
|
-
|
|
4207
|
-
"mouseenter.hover",
|
|
4208
|
-
function(_event, node) {
|
|
4212
|
+
const firstNode = nodeSelection.node();
|
|
4213
|
+
if (!firstNode) return;
|
|
4214
|
+
if (hoverStyle) {
|
|
4215
|
+
nodeSelection.on("mouseenter.hover", function(_event, node) {
|
|
4209
4216
|
const circle = this;
|
|
4210
|
-
|
|
4211
|
-
|
|
4212
|
-
|
|
4213
|
-
|
|
4214
|
-
"stroke",
|
|
4215
|
-
hoverStroke
|
|
4216
|
-
);
|
|
4217
|
-
circle.setAttribute(
|
|
4218
|
-
"stroke-width",
|
|
4219
|
-
String(
|
|
4220
|
-
hoverStrokeWidth
|
|
4221
|
-
)
|
|
4222
|
-
);
|
|
4223
|
-
circle.setAttribute(
|
|
4224
|
-
"opacity",
|
|
4225
|
-
String(
|
|
4226
|
-
hoverOpacity
|
|
4227
|
-
)
|
|
4228
|
-
);
|
|
4229
|
-
}
|
|
4230
|
-
).on(
|
|
4231
|
-
"mouseleave.hover",
|
|
4232
|
-
function(_event, node) {
|
|
4217
|
+
circle.setAttribute("stroke", hoverStyle.stroke ?? node.style?.stroke ?? "#ffffff");
|
|
4218
|
+
circle.setAttribute("stroke-width", String(hoverStyle.strokeWidth ?? node.style?.strokeWidth ?? 1.5));
|
|
4219
|
+
circle.setAttribute("opacity", String(hoverStyle.opacity ?? node.style?.opacity ?? 1));
|
|
4220
|
+
}).on("mouseleave.hover", function(_event, node) {
|
|
4233
4221
|
const circle = this;
|
|
4234
|
-
|
|
4235
|
-
|
|
4236
|
-
|
|
4237
|
-
|
|
4238
|
-
|
|
4239
|
-
|
|
4240
|
-
|
|
4241
|
-
|
|
4242
|
-
|
|
4243
|
-
|
|
4244
|
-
|
|
4245
|
-
|
|
4246
|
-
|
|
4247
|
-
|
|
4248
|
-
|
|
4249
|
-
|
|
4250
|
-
|
|
4251
|
-
|
|
4252
|
-
|
|
4253
|
-
}
|
|
4254
|
-
);
|
|
4222
|
+
circle.setAttribute("stroke", node.style?.stroke ?? "#ffffff");
|
|
4223
|
+
circle.setAttribute("stroke-width", String(node.style?.strokeWidth ?? 1.5));
|
|
4224
|
+
circle.setAttribute("opacity", String(node.style?.opacity ?? 1));
|
|
4225
|
+
});
|
|
4226
|
+
}
|
|
4227
|
+
const svgElement = firstNode.ownerSVGElement;
|
|
4228
|
+
if (!svgElement) return;
|
|
4229
|
+
const root2 = select_default2(svgElement);
|
|
4230
|
+
const labelSelection = root2.selectAll(".link-label");
|
|
4231
|
+
nodeSelection.on("mouseenter.labels", (_event, d) => {
|
|
4232
|
+
labelSelection.filter((item) => {
|
|
4233
|
+
if (item.style.label.visibility !== "hover") return false;
|
|
4234
|
+
const s = item.link.source;
|
|
4235
|
+
const t = item.link.target;
|
|
4236
|
+
return s.id === d.id || t.id === d.id;
|
|
4237
|
+
}).interrupt().transition().duration(200).style("opacity", 1).style("pointer-events", "auto");
|
|
4238
|
+
}).on("mouseleave.labels", (_event) => {
|
|
4239
|
+
labelSelection.filter((item) => item.style.label.visibility === "hover").interrupt().transition().duration(200).style("opacity", 0).style("pointer-events", "none");
|
|
4240
|
+
});
|
|
4255
4241
|
}
|
|
4256
4242
|
|
|
4257
4243
|
// src/utils/resolve-tooltip-position.ts
|
|
@@ -4456,10 +4442,11 @@ function observeResize(element, onResize) {
|
|
|
4456
4442
|
if (!entry) {
|
|
4457
4443
|
return;
|
|
4458
4444
|
}
|
|
4459
|
-
|
|
4460
|
-
|
|
4461
|
-
|
|
4462
|
-
|
|
4445
|
+
const width = entry.borderBoxSize?.[0]?.inlineSize ?? entry.contentRect.width;
|
|
4446
|
+
const height = entry.borderBoxSize?.[0]?.blockSize ?? entry.contentRect.height;
|
|
4447
|
+
if (width > 0 && height > 0) {
|
|
4448
|
+
onResize(width, height);
|
|
4449
|
+
}
|
|
4463
4450
|
}
|
|
4464
4451
|
);
|
|
4465
4452
|
observer.observe(element);
|
|
@@ -4492,54 +4479,94 @@ function getLinkTargetPoint(link) {
|
|
|
4492
4479
|
};
|
|
4493
4480
|
}
|
|
4494
4481
|
|
|
4482
|
+
// src/utils/export-graph.ts
|
|
4483
|
+
var import_html2canvas = __toESM(require("html2canvas"), 1);
|
|
4484
|
+
async function captureAndDownloadGraph(container, options = {}) {
|
|
4485
|
+
const {
|
|
4486
|
+
fileName = `graph-export-${Date.now()}.png`,
|
|
4487
|
+
backgroundColor = "#ffffff",
|
|
4488
|
+
pixelRatio = 2
|
|
4489
|
+
} = options;
|
|
4490
|
+
const root2 = container.querySelector(".pg-root");
|
|
4491
|
+
if (!root2) return;
|
|
4492
|
+
const controls = root2.querySelector(".pg-controls");
|
|
4493
|
+
const legendToggle = root2.querySelector(".pg-legend-toggle");
|
|
4494
|
+
const interactionLayer = root2.querySelector(".pg-interaction-layer");
|
|
4495
|
+
const legend = root2.querySelector(".pg-legend");
|
|
4496
|
+
const wasCollapsed = legend?.classList.contains("pg-is-collapsed");
|
|
4497
|
+
if (controls) controls.style.display = "none";
|
|
4498
|
+
if (legendToggle) legendToggle.style.display = "none";
|
|
4499
|
+
if (interactionLayer) interactionLayer.style.display = "none";
|
|
4500
|
+
if (legend && wasCollapsed) {
|
|
4501
|
+
legend.classList.remove("pg-is-collapsed");
|
|
4502
|
+
}
|
|
4503
|
+
try {
|
|
4504
|
+
const canvas = await (0, import_html2canvas.default)(root2, {
|
|
4505
|
+
scale: pixelRatio,
|
|
4506
|
+
backgroundColor,
|
|
4507
|
+
useCORS: true,
|
|
4508
|
+
logging: false
|
|
4509
|
+
});
|
|
4510
|
+
const dataUrl = canvas.toDataURL("image/png");
|
|
4511
|
+
const link = document.createElement("a");
|
|
4512
|
+
link.download = fileName;
|
|
4513
|
+
link.href = dataUrl;
|
|
4514
|
+
link.click();
|
|
4515
|
+
} finally {
|
|
4516
|
+
if (controls) controls.style.display = "flex";
|
|
4517
|
+
if (legendToggle) legendToggle.style.display = "flex";
|
|
4518
|
+
if (interactionLayer) interactionLayer.style.display = "block";
|
|
4519
|
+
if (legend && wasCollapsed) {
|
|
4520
|
+
legend.classList.add("pg-is-collapsed");
|
|
4521
|
+
}
|
|
4522
|
+
}
|
|
4523
|
+
}
|
|
4524
|
+
|
|
4495
4525
|
// src/create-graph.ts
|
|
4496
4526
|
function createGraph(config) {
|
|
4497
4527
|
let cleanupResize = null;
|
|
4498
4528
|
let cleanupZoom = null;
|
|
4499
4529
|
let tooltipBinding = null;
|
|
4500
4530
|
let controls = null;
|
|
4531
|
+
let legendCleanup = null;
|
|
4532
|
+
let fitViewTimer = null;
|
|
4501
4533
|
let dimensions = { width: 0, height: 0 };
|
|
4502
4534
|
let rootGroup = null;
|
|
4535
|
+
let svgElement = null;
|
|
4503
4536
|
let zoomBehavior = null;
|
|
4504
4537
|
let simulation = null;
|
|
4505
4538
|
function render() {
|
|
4506
4539
|
destroy();
|
|
4507
4540
|
const layers = createGraphLayers(config.container);
|
|
4541
|
+
svgElement = layers.svg;
|
|
4508
4542
|
rootGroup = layers.root;
|
|
4509
|
-
cleanupResize = observeResize(
|
|
4510
|
-
|
|
4511
|
-
(width,
|
|
4512
|
-
|
|
4513
|
-
|
|
4514
|
-
|
|
4515
|
-
|
|
4516
|
-
simulation
|
|
4517
|
-
simulation
|
|
4543
|
+
cleanupResize = observeResize(config.container, (width, height) => {
|
|
4544
|
+
dimensions = { width, height };
|
|
4545
|
+
layers.svg.setAttribute("width", String(width));
|
|
4546
|
+
layers.svg.setAttribute("height", String(height));
|
|
4547
|
+
layers.interactionRect.setAttribute("width", String(width));
|
|
4548
|
+
layers.interactionRect.setAttribute("height", String(height));
|
|
4549
|
+
if (simulation) {
|
|
4550
|
+
simulation.force("center", center_default(width / 2, height / 2));
|
|
4551
|
+
simulation.alpha(0.3).restart();
|
|
4518
4552
|
}
|
|
4519
|
-
|
|
4553
|
+
if (fitViewTimer) {
|
|
4554
|
+
clearTimeout(fitViewTimer);
|
|
4555
|
+
}
|
|
4556
|
+
fitViewTimer = setTimeout(() => {
|
|
4557
|
+
fitView();
|
|
4558
|
+
fitViewTimer = null;
|
|
4559
|
+
}, 150);
|
|
4560
|
+
});
|
|
4520
4561
|
const zoomResult = createZoom({
|
|
4521
|
-
|
|
4522
|
-
* D3 zoom must be attached to SVG
|
|
4523
|
-
* because it requires:
|
|
4524
|
-
*
|
|
4525
|
-
* width.baseVal
|
|
4526
|
-
* height.baseVal
|
|
4527
|
-
*/
|
|
4528
|
-
svg: config.container,
|
|
4529
|
-
/**
|
|
4530
|
-
* Used for pointer semantics /
|
|
4531
|
-
* pan filtering only
|
|
4532
|
-
*/
|
|
4562
|
+
svg: layers.svg,
|
|
4533
4563
|
interactionLayer: layers.interactionLayer,
|
|
4534
|
-
/**
|
|
4535
|
-
* Actual graph transform target
|
|
4536
|
-
*/
|
|
4537
4564
|
root: layers.root
|
|
4538
4565
|
});
|
|
4539
4566
|
zoomBehavior = zoomResult.behavior;
|
|
4540
4567
|
cleanupZoom = zoomResult.cleanup;
|
|
4541
4568
|
const root2 = select_default2(layers.root);
|
|
4542
|
-
const renderContext = { svg:
|
|
4569
|
+
const renderContext = { svg: layers.svg, root: root2, interaction: config.interaction };
|
|
4543
4570
|
const linkSelection = renderLinks(renderContext, config.links);
|
|
4544
4571
|
const linkLabelSelection = renderLinkLabels(renderContext, config.links);
|
|
4545
4572
|
const nodeSelection = renderNodes(renderContext, config.nodes);
|
|
@@ -4547,45 +4574,41 @@ function createGraph(config) {
|
|
|
4547
4574
|
const simulationConfig = {
|
|
4548
4575
|
nodes: config.nodes,
|
|
4549
4576
|
links: config.links,
|
|
4550
|
-
|
|
4551
|
-
|
|
4577
|
+
// Uses the observed dimensions to ensure physics are calculated on actual container size
|
|
4578
|
+
width: dimensions.width || config.container.clientWidth,
|
|
4579
|
+
height: dimensions.height || config.container.clientHeight
|
|
4552
4580
|
};
|
|
4553
4581
|
const simulationResult = createGraphSimulation(simulationConfig);
|
|
4554
4582
|
simulation = simulationResult.simulation;
|
|
4555
|
-
simulation.on(
|
|
4556
|
-
"
|
|
4557
|
-
() => {
|
|
4558
|
-
|
|
4559
|
-
|
|
4560
|
-
|
|
4561
|
-
|
|
4562
|
-
|
|
4563
|
-
|
|
4564
|
-
|
|
4565
|
-
|
|
4566
|
-
|
|
4567
|
-
|
|
4568
|
-
|
|
4569
|
-
|
|
4570
|
-
|
|
4571
|
-
|
|
4572
|
-
|
|
4573
|
-
|
|
4574
|
-
|
|
4575
|
-
|
|
4576
|
-
|
|
4577
|
-
|
|
4578
|
-
|
|
4579
|
-
|
|
4580
|
-
nodeSelection.attr("cx", (d) => d.x ?? 0).attr("cy", (d) => d.y ?? 0);
|
|
4581
|
-
labelSelection.attr("x", (d) => d.x ?? 0).attr("y", (d) => d.y ?? 0);
|
|
4582
|
-
tooltipBinding?.reposition();
|
|
4583
|
-
}
|
|
4584
|
-
);
|
|
4583
|
+
simulation.on("tick", () => {
|
|
4584
|
+
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);
|
|
4585
|
+
linkLabelSelection.attr("transform", (item) => {
|
|
4586
|
+
const link = item.link;
|
|
4587
|
+
const source = link.source;
|
|
4588
|
+
const targetPoint = getLinkTargetPoint(link);
|
|
4589
|
+
const x3 = ((source.x ?? 0) + targetPoint.x) / 2;
|
|
4590
|
+
const y3 = ((source.y ?? 0) + targetPoint.y) / 2;
|
|
4591
|
+
return `translate(${x3}, ${y3})`;
|
|
4592
|
+
}).each(function() {
|
|
4593
|
+
const group = this;
|
|
4594
|
+
const text = group.querySelector("text");
|
|
4595
|
+
const rect = group.querySelector("rect");
|
|
4596
|
+
if (!text || !rect) return;
|
|
4597
|
+
const bBox = text.getBBox();
|
|
4598
|
+
const padding = 6;
|
|
4599
|
+
rect.setAttribute("x", String(bBox.x - padding));
|
|
4600
|
+
rect.setAttribute("y", String(bBox.y - padding));
|
|
4601
|
+
rect.setAttribute("width", String(bBox.width + padding * 2));
|
|
4602
|
+
rect.setAttribute("height", String(bBox.height + padding * 2));
|
|
4603
|
+
});
|
|
4604
|
+
nodeSelection.attr("cx", (d) => d.x ?? 0).attr("cy", (d) => d.y ?? 0);
|
|
4605
|
+
labelSelection.attr("x", (d) => d.x ?? 0).attr("y", (d) => d.y ?? 0);
|
|
4606
|
+
tooltipBinding?.reposition();
|
|
4607
|
+
});
|
|
4585
4608
|
if (config.interaction?.hover?.enabled) {
|
|
4586
4609
|
if (config.interaction?.hover?.tooltip?.enabled) {
|
|
4587
4610
|
tooltipBinding = bindNodeTooltip({
|
|
4588
|
-
container: config.container
|
|
4611
|
+
container: config.container,
|
|
4589
4612
|
selection: nodeSelection,
|
|
4590
4613
|
tooltipConfig: config.interaction.hover.tooltip
|
|
4591
4614
|
});
|
|
@@ -4595,66 +4618,53 @@ function createGraph(config) {
|
|
|
4595
4618
|
if (config.interaction?.drag?.enabled !== false) {
|
|
4596
4619
|
nodeSelection.call(createDragBehavior(simulation));
|
|
4597
4620
|
}
|
|
4598
|
-
if (config.interaction?.selection?.enabled) {
|
|
4599
|
-
}
|
|
4600
4621
|
if (config.controls?.enabled) {
|
|
4601
|
-
controls = createGraphControls(
|
|
4622
|
+
controls = createGraphControls(
|
|
4623
|
+
layers.overlay,
|
|
4624
|
+
{ zoomIn, zoomOut, resetView, fitView, destroy, render, exportGraph },
|
|
4625
|
+
config.controls
|
|
4626
|
+
);
|
|
4602
4627
|
controls.mount();
|
|
4603
4628
|
}
|
|
4629
|
+
if (config.legend?.enabled) {
|
|
4630
|
+
legendCleanup = createGraphLegend(layers.overlay, config.legend);
|
|
4631
|
+
}
|
|
4604
4632
|
}
|
|
4605
4633
|
function resetView() {
|
|
4606
|
-
if (!zoomBehavior)
|
|
4607
|
-
|
|
4608
|
-
}
|
|
4609
|
-
select_default2(config.container).transition().call(
|
|
4610
|
-
zoomBehavior.transform,
|
|
4611
|
-
identity2
|
|
4612
|
-
);
|
|
4634
|
+
if (!zoomBehavior || !svgElement) return;
|
|
4635
|
+
select_default2(svgElement).transition().duration(400).call(zoomBehavior.transform, identity2);
|
|
4613
4636
|
}
|
|
4614
4637
|
function fitView() {
|
|
4615
|
-
if (!zoomBehavior || !rootGroup || dimensions.width === 0 || dimensions.height === 0)
|
|
4616
|
-
return;
|
|
4617
|
-
}
|
|
4638
|
+
if (!zoomBehavior || !rootGroup || !svgElement || dimensions.width === 0 || dimensions.height === 0) return;
|
|
4618
4639
|
const bounds = rootGroup.getBBox();
|
|
4619
|
-
if (bounds.width === 0 || bounds.height === 0)
|
|
4620
|
-
|
|
4621
|
-
|
|
4622
|
-
const
|
|
4623
|
-
const
|
|
4624
|
-
|
|
4625
|
-
width / bounds.width,
|
|
4626
|
-
height / bounds.height
|
|
4627
|
-
) * 0.9;
|
|
4628
|
-
const translateX = (width - bounds.width * scale) / 2 - bounds.x * scale;
|
|
4629
|
-
const translateY = (height - bounds.height * scale) / 2 - bounds.y * scale;
|
|
4630
|
-
const transform2 = identity2.translate(
|
|
4631
|
-
translateX,
|
|
4632
|
-
translateY
|
|
4633
|
-
).scale(scale);
|
|
4634
|
-
select_default2(config.container).transition().call(
|
|
4635
|
-
zoomBehavior.transform,
|
|
4636
|
-
transform2
|
|
4637
|
-
);
|
|
4640
|
+
if (bounds.width === 0 || bounds.height === 0) return;
|
|
4641
|
+
const scale = Math.min(dimensions.width / bounds.width, dimensions.height / bounds.height) * 0.9;
|
|
4642
|
+
const translateX = (dimensions.width - bounds.width * scale) / 2 - bounds.x * scale;
|
|
4643
|
+
const translateY = (dimensions.height - bounds.height * scale) / 2 - bounds.y * scale;
|
|
4644
|
+
const transform2 = identity2.translate(translateX, translateY).scale(scale);
|
|
4645
|
+
select_default2(svgElement).transition().duration(400).call(zoomBehavior.transform, transform2);
|
|
4638
4646
|
}
|
|
4639
4647
|
function zoomIn() {
|
|
4640
|
-
if (!zoomBehavior)
|
|
4641
|
-
|
|
4642
|
-
}
|
|
4643
|
-
select_default2(config.container).transition().call(
|
|
4644
|
-
zoomBehavior.scaleBy,
|
|
4645
|
-
1.2
|
|
4646
|
-
);
|
|
4648
|
+
if (!zoomBehavior || !svgElement) return;
|
|
4649
|
+
select_default2(svgElement).transition().call(zoomBehavior.scaleBy, 1.2);
|
|
4647
4650
|
}
|
|
4648
4651
|
function zoomOut() {
|
|
4649
|
-
if (!zoomBehavior)
|
|
4650
|
-
|
|
4651
|
-
|
|
4652
|
-
|
|
4653
|
-
|
|
4654
|
-
|
|
4655
|
-
|
|
4652
|
+
if (!zoomBehavior || !svgElement) return;
|
|
4653
|
+
select_default2(svgElement).transition().call(zoomBehavior.scaleBy, 0.8);
|
|
4654
|
+
}
|
|
4655
|
+
async function exportGraph(fileName) {
|
|
4656
|
+
fitView();
|
|
4657
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
4658
|
+
await captureAndDownloadGraph(config.container, {
|
|
4659
|
+
fileName,
|
|
4660
|
+
pixelRatio: 2
|
|
4661
|
+
});
|
|
4656
4662
|
}
|
|
4657
4663
|
function destroy() {
|
|
4664
|
+
if (fitViewTimer) {
|
|
4665
|
+
clearTimeout(fitViewTimer);
|
|
4666
|
+
fitViewTimer = null;
|
|
4667
|
+
}
|
|
4658
4668
|
if (cleanupResize) {
|
|
4659
4669
|
cleanupResize();
|
|
4660
4670
|
cleanupResize = null;
|
|
@@ -4675,13 +4685,18 @@ function createGraph(config) {
|
|
|
4675
4685
|
controls.destroy();
|
|
4676
4686
|
controls = null;
|
|
4677
4687
|
}
|
|
4688
|
+
if (legendCleanup) {
|
|
4689
|
+
legendCleanup();
|
|
4690
|
+
legendCleanup = null;
|
|
4691
|
+
}
|
|
4678
4692
|
rootGroup = null;
|
|
4693
|
+
svgElement = null;
|
|
4679
4694
|
zoomBehavior = null;
|
|
4680
4695
|
while (config.container.firstChild) {
|
|
4681
4696
|
config.container.removeChild(config.container.firstChild);
|
|
4682
4697
|
}
|
|
4683
4698
|
}
|
|
4684
|
-
return { render, zoomIn, zoomOut, resetView, fitView, destroy };
|
|
4699
|
+
return { render, zoomIn, zoomOut, resetView, fitView, destroy, exportGraph };
|
|
4685
4700
|
}
|
|
4686
4701
|
// Annotate the CommonJS export names for ESM import in node:
|
|
4687
4702
|
0 && (module.exports = {
|