polly-graph 0.1.4 → 0.1.6
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 +293 -181
- package/dist/index.d.cts +12 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +293 -181
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -3644,68 +3644,6 @@ function manyBody_default() {
|
|
|
3644
3644
|
return force;
|
|
3645
3645
|
}
|
|
3646
3646
|
|
|
3647
|
-
// node_modules/d3-force/src/x.js
|
|
3648
|
-
function x_default2(x3) {
|
|
3649
|
-
var strength = constant_default5(0.1), nodes, strengths, xz;
|
|
3650
|
-
if (typeof x3 !== "function") x3 = constant_default5(x3 == null ? 0 : +x3);
|
|
3651
|
-
function force(alpha) {
|
|
3652
|
-
for (var i = 0, n = nodes.length, node; i < n; ++i) {
|
|
3653
|
-
node = nodes[i], node.vx += (xz[i] - node.x) * strengths[i] * alpha;
|
|
3654
|
-
}
|
|
3655
|
-
}
|
|
3656
|
-
function initialize() {
|
|
3657
|
-
if (!nodes) return;
|
|
3658
|
-
var i, n = nodes.length;
|
|
3659
|
-
strengths = new Array(n);
|
|
3660
|
-
xz = new Array(n);
|
|
3661
|
-
for (i = 0; i < n; ++i) {
|
|
3662
|
-
strengths[i] = isNaN(xz[i] = +x3(nodes[i], i, nodes)) ? 0 : +strength(nodes[i], i, nodes);
|
|
3663
|
-
}
|
|
3664
|
-
}
|
|
3665
|
-
force.initialize = function(_) {
|
|
3666
|
-
nodes = _;
|
|
3667
|
-
initialize();
|
|
3668
|
-
};
|
|
3669
|
-
force.strength = function(_) {
|
|
3670
|
-
return arguments.length ? (strength = typeof _ === "function" ? _ : constant_default5(+_), initialize(), force) : strength;
|
|
3671
|
-
};
|
|
3672
|
-
force.x = function(_) {
|
|
3673
|
-
return arguments.length ? (x3 = typeof _ === "function" ? _ : constant_default5(+_), initialize(), force) : x3;
|
|
3674
|
-
};
|
|
3675
|
-
return force;
|
|
3676
|
-
}
|
|
3677
|
-
|
|
3678
|
-
// node_modules/d3-force/src/y.js
|
|
3679
|
-
function y_default2(y3) {
|
|
3680
|
-
var strength = constant_default5(0.1), nodes, strengths, yz;
|
|
3681
|
-
if (typeof y3 !== "function") y3 = constant_default5(y3 == null ? 0 : +y3);
|
|
3682
|
-
function force(alpha) {
|
|
3683
|
-
for (var i = 0, n = nodes.length, node; i < n; ++i) {
|
|
3684
|
-
node = nodes[i], node.vy += (yz[i] - node.y) * strengths[i] * alpha;
|
|
3685
|
-
}
|
|
3686
|
-
}
|
|
3687
|
-
function initialize() {
|
|
3688
|
-
if (!nodes) return;
|
|
3689
|
-
var i, n = nodes.length;
|
|
3690
|
-
strengths = new Array(n);
|
|
3691
|
-
yz = new Array(n);
|
|
3692
|
-
for (i = 0; i < n; ++i) {
|
|
3693
|
-
strengths[i] = isNaN(yz[i] = +y3(nodes[i], i, nodes)) ? 0 : +strength(nodes[i], i, nodes);
|
|
3694
|
-
}
|
|
3695
|
-
}
|
|
3696
|
-
force.initialize = function(_) {
|
|
3697
|
-
nodes = _;
|
|
3698
|
-
initialize();
|
|
3699
|
-
};
|
|
3700
|
-
force.strength = function(_) {
|
|
3701
|
-
return arguments.length ? (strength = typeof _ === "function" ? _ : constant_default5(+_), initialize(), force) : strength;
|
|
3702
|
-
};
|
|
3703
|
-
force.y = function(_) {
|
|
3704
|
-
return arguments.length ? (y3 = typeof _ === "function" ? _ : constant_default5(+_), initialize(), force) : y3;
|
|
3705
|
-
};
|
|
3706
|
-
return force;
|
|
3707
|
-
}
|
|
3708
|
-
|
|
3709
3647
|
// src/core/create-graph-layers.ts
|
|
3710
3648
|
function createGraphLayers(host) {
|
|
3711
3649
|
host.innerHTML = "";
|
|
@@ -3814,14 +3752,71 @@ function createGraphSimulation(config) {
|
|
|
3814
3752
|
});
|
|
3815
3753
|
const simulation = simulation_default(config.nodes).alpha(0.9).alphaDecay(0.12).alphaMin(0.03).velocityDecay(0.5).force(
|
|
3816
3754
|
"link",
|
|
3817
|
-
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)
|
|
3818
3763
|
).force("charge", manyBody_default().strength(-220)).force(
|
|
3819
3764
|
"collide",
|
|
3820
|
-
collide_default().radius((node) => (node.style?.radius ?? 12) + 10).
|
|
3765
|
+
collide_default().radius((node) => (node.style?.radius ?? 12) + 10).iterations(2)
|
|
3821
3766
|
).force("center", center_default(centerX, centerY).strength(0.08));
|
|
3822
3767
|
return { simulation };
|
|
3823
3768
|
}
|
|
3824
3769
|
|
|
3770
|
+
// src/utils/get-link-marker-id.ts
|
|
3771
|
+
function getLinkMarkerId(style) {
|
|
3772
|
+
const markerStyle = {
|
|
3773
|
+
stroke: style.stroke ?? "#94a3b8",
|
|
3774
|
+
strokeWidth: style.strokeWidth ?? 2,
|
|
3775
|
+
arrowFill: style.arrow?.fill ?? style.stroke ?? "#94a3b8",
|
|
3776
|
+
arrowSize: style.arrow?.size ?? 6
|
|
3777
|
+
};
|
|
3778
|
+
const serializedStyle = JSON.stringify(markerStyle);
|
|
3779
|
+
const hash = createHash(serializedStyle);
|
|
3780
|
+
return `graph-arrow-${hash}`;
|
|
3781
|
+
}
|
|
3782
|
+
function createHash(value) {
|
|
3783
|
+
let hash = 0;
|
|
3784
|
+
for (let index2 = 0; index2 < value.length; index2 += 1) {
|
|
3785
|
+
const charCode = value.charCodeAt(index2);
|
|
3786
|
+
hash = (hash << 5) - hash + charCode;
|
|
3787
|
+
hash |= 0;
|
|
3788
|
+
}
|
|
3789
|
+
return Math.abs(hash).toString(36);
|
|
3790
|
+
}
|
|
3791
|
+
|
|
3792
|
+
// src/core/create-arrow-marker.ts
|
|
3793
|
+
function createArrowMarker(params) {
|
|
3794
|
+
const markerId = getLinkMarkerId(params.style);
|
|
3795
|
+
const existingMarker = params.svg.querySelector(`#${markerId}`);
|
|
3796
|
+
if (existingMarker) {
|
|
3797
|
+
return markerId;
|
|
3798
|
+
}
|
|
3799
|
+
const arrowSize = params.style.arrow?.size ?? 6;
|
|
3800
|
+
const fill = params.style.arrow?.fill ?? params.style.stroke ?? "#94a3b8";
|
|
3801
|
+
const defs = document.createElementNS("http://www.w3.org/2000/svg", "defs");
|
|
3802
|
+
const marker = document.createElementNS("http://www.w3.org/2000/svg", "marker");
|
|
3803
|
+
marker.setAttribute("id", markerId);
|
|
3804
|
+
marker.setAttribute("viewBox", "0 0 20 20");
|
|
3805
|
+
marker.setAttribute("refX", "0");
|
|
3806
|
+
marker.setAttribute("refY", "10");
|
|
3807
|
+
marker.setAttribute("markerWidth", String(arrowSize * 2));
|
|
3808
|
+
marker.setAttribute("markerHeight", String(arrowSize * 2));
|
|
3809
|
+
marker.setAttribute("orient", "auto");
|
|
3810
|
+
marker.setAttribute("markerUnits", "userSpaceOnUse");
|
|
3811
|
+
const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
|
|
3812
|
+
path.setAttribute("d", "M 0 0 L 20 10 L 0 20 z");
|
|
3813
|
+
path.setAttribute("fill", fill);
|
|
3814
|
+
marker.appendChild(path);
|
|
3815
|
+
defs.appendChild(marker);
|
|
3816
|
+
params.svg.insertBefore(defs, params.svg.firstChild);
|
|
3817
|
+
return markerId;
|
|
3818
|
+
}
|
|
3819
|
+
|
|
3825
3820
|
// src/controls/graph-controls.utils.ts
|
|
3826
3821
|
function resolveControlsPosition(position) {
|
|
3827
3822
|
return position ?? "bottom-left";
|
|
@@ -4000,6 +3995,7 @@ var DEFAULT_LINK_STYLE = {
|
|
|
4000
3995
|
},
|
|
4001
3996
|
label: {
|
|
4002
3997
|
enabled: true,
|
|
3998
|
+
visibility: "always",
|
|
4003
3999
|
backgroundFill: "color-mix(in srgb, #8E42EE, #FFFFFF 90%)",
|
|
4004
4000
|
borderColor: "color-mix(in srgb, #8E42EE, #FFFFFF 10%)",
|
|
4005
4001
|
borderWidth: 1.5,
|
|
@@ -4035,6 +4031,7 @@ function mergeLinkStyle(base, override) {
|
|
|
4035
4031
|
},
|
|
4036
4032
|
label: {
|
|
4037
4033
|
enabled: override?.label?.enabled ?? base.label.enabled,
|
|
4034
|
+
visibility: override?.label?.visibility ?? base.label.visibility,
|
|
4038
4035
|
backgroundFill: override?.label?.backgroundFill ?? base.label.backgroundFill,
|
|
4039
4036
|
borderColor: override?.label?.borderColor ?? base.label.borderColor,
|
|
4040
4037
|
borderWidth: override?.label?.borderWidth ?? base.label.borderWidth,
|
|
@@ -4048,56 +4045,6 @@ function mergeLinkStyle(base, override) {
|
|
|
4048
4045
|
};
|
|
4049
4046
|
}
|
|
4050
4047
|
|
|
4051
|
-
// src/utils/get-link-marker-id.ts
|
|
4052
|
-
function getLinkMarkerId(style) {
|
|
4053
|
-
const markerStyle = {
|
|
4054
|
-
stroke: style.stroke ?? "#94a3b8",
|
|
4055
|
-
strokeWidth: style.strokeWidth ?? 2,
|
|
4056
|
-
arrowFill: style.arrow?.fill ?? style.stroke ?? "#94a3b8",
|
|
4057
|
-
arrowSize: style.arrow?.size ?? 6
|
|
4058
|
-
};
|
|
4059
|
-
const serializedStyle = JSON.stringify(markerStyle);
|
|
4060
|
-
const hash = createHash(serializedStyle);
|
|
4061
|
-
return `graph-arrow-${hash}`;
|
|
4062
|
-
}
|
|
4063
|
-
function createHash(value) {
|
|
4064
|
-
let hash = 0;
|
|
4065
|
-
for (let index2 = 0; index2 < value.length; index2 += 1) {
|
|
4066
|
-
const charCode = value.charCodeAt(index2);
|
|
4067
|
-
hash = (hash << 5) - hash + charCode;
|
|
4068
|
-
hash |= 0;
|
|
4069
|
-
}
|
|
4070
|
-
return Math.abs(hash).toString(36);
|
|
4071
|
-
}
|
|
4072
|
-
|
|
4073
|
-
// src/core/create-arrow-marker.ts
|
|
4074
|
-
function createArrowMarker(params) {
|
|
4075
|
-
const markerId = getLinkMarkerId(params.style);
|
|
4076
|
-
const existingMarker = params.svg.querySelector(`#${markerId}`);
|
|
4077
|
-
if (existingMarker) {
|
|
4078
|
-
return markerId;
|
|
4079
|
-
}
|
|
4080
|
-
const arrowSize = params.style.arrow?.size ?? 6;
|
|
4081
|
-
const fill = params.style.arrow?.fill ?? params.style.stroke ?? "#94a3b8";
|
|
4082
|
-
const defs = document.createElementNS("http://www.w3.org/2000/svg", "defs");
|
|
4083
|
-
const marker = document.createElementNS("http://www.w3.org/2000/svg", "marker");
|
|
4084
|
-
marker.setAttribute("id", markerId);
|
|
4085
|
-
marker.setAttribute("viewBox", "0 0 20 20");
|
|
4086
|
-
marker.setAttribute("refX", "0");
|
|
4087
|
-
marker.setAttribute("refY", "10");
|
|
4088
|
-
marker.setAttribute("markerWidth", String(arrowSize * 2));
|
|
4089
|
-
marker.setAttribute("markerHeight", String(arrowSize * 2));
|
|
4090
|
-
marker.setAttribute("orient", "auto");
|
|
4091
|
-
marker.setAttribute("markerUnits", "userSpaceOnUse");
|
|
4092
|
-
const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
|
|
4093
|
-
path.setAttribute("d", "M 0 0 L 20 10 L 0 20 z");
|
|
4094
|
-
path.setAttribute("fill", fill);
|
|
4095
|
-
marker.appendChild(path);
|
|
4096
|
-
defs.appendChild(marker);
|
|
4097
|
-
params.svg.insertBefore(defs, params.svg.firstChild);
|
|
4098
|
-
return markerId;
|
|
4099
|
-
}
|
|
4100
|
-
|
|
4101
4048
|
// src/renderer/links.ts
|
|
4102
4049
|
function getShortenedTargetPoint(link, style) {
|
|
4103
4050
|
const source = link.source;
|
|
@@ -4135,7 +4082,14 @@ function getLinkKey(link) {
|
|
|
4135
4082
|
}
|
|
4136
4083
|
function renderLinks(ctx, links) {
|
|
4137
4084
|
const renderableLinks = createRenderableLinks(ctx, links);
|
|
4138
|
-
|
|
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").style("cursor", "pointer");
|
|
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;
|
|
4139
4093
|
}
|
|
4140
4094
|
|
|
4141
4095
|
// src/renderer/nodes.ts
|
|
@@ -4205,7 +4159,10 @@ function renderNodeLabels(ctx, nodes) {
|
|
|
4205
4159
|
// src/renderer/link-labels.ts
|
|
4206
4160
|
function createRenderableLinks2(params, links) {
|
|
4207
4161
|
return links.map(
|
|
4208
|
-
(link) => ({
|
|
4162
|
+
(link) => ({
|
|
4163
|
+
link,
|
|
4164
|
+
style: resolveLinkStyle({ link, interaction: params.interaction })
|
|
4165
|
+
})
|
|
4209
4166
|
).filter(
|
|
4210
4167
|
(item) => item.style.label.enabled && Boolean(item.link.label)
|
|
4211
4168
|
);
|
|
@@ -4217,7 +4174,13 @@ function getLinkKey2(link) {
|
|
|
4217
4174
|
}
|
|
4218
4175
|
function renderLinkLabels(params, links) {
|
|
4219
4176
|
const renderableLinks = createRenderableLinks2(params, links);
|
|
4220
|
-
const labelSelection = params.root.select('[data-layer="link-labels"]').selectAll(".link-label").data(renderableLinks, (item) => getLinkKey2(item.link)).join("g").attr("class", "link-label").
|
|
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");
|
|
4221
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);
|
|
4222
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 ?? "");
|
|
4223
4186
|
return labelSelection;
|
|
@@ -4246,58 +4209,37 @@ function createDragBehavior(simulation) {
|
|
|
4246
4209
|
|
|
4247
4210
|
// src/interactions/create-node-hover.ts
|
|
4248
4211
|
function createNodeHover(nodeSelection, hoverStyle) {
|
|
4249
|
-
|
|
4250
|
-
|
|
4251
|
-
|
|
4252
|
-
|
|
4253
|
-
"mouseenter.hover",
|
|
4254
|
-
function(_event, node) {
|
|
4212
|
+
const firstNode = nodeSelection.node();
|
|
4213
|
+
if (!firstNode) return;
|
|
4214
|
+
if (hoverStyle) {
|
|
4215
|
+
nodeSelection.on("mouseenter.hover", function(_event, node) {
|
|
4255
4216
|
const circle = this;
|
|
4256
|
-
|
|
4257
|
-
|
|
4258
|
-
|
|
4259
|
-
|
|
4260
|
-
"stroke",
|
|
4261
|
-
hoverStroke
|
|
4262
|
-
);
|
|
4263
|
-
circle.setAttribute(
|
|
4264
|
-
"stroke-width",
|
|
4265
|
-
String(
|
|
4266
|
-
hoverStrokeWidth
|
|
4267
|
-
)
|
|
4268
|
-
);
|
|
4269
|
-
circle.setAttribute(
|
|
4270
|
-
"opacity",
|
|
4271
|
-
String(
|
|
4272
|
-
hoverOpacity
|
|
4273
|
-
)
|
|
4274
|
-
);
|
|
4275
|
-
}
|
|
4276
|
-
).on(
|
|
4277
|
-
"mouseleave.hover",
|
|
4278
|
-
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) {
|
|
4279
4221
|
const circle = this;
|
|
4280
|
-
|
|
4281
|
-
|
|
4282
|
-
|
|
4283
|
-
|
|
4284
|
-
|
|
4285
|
-
|
|
4286
|
-
|
|
4287
|
-
|
|
4288
|
-
|
|
4289
|
-
|
|
4290
|
-
|
|
4291
|
-
|
|
4292
|
-
|
|
4293
|
-
|
|
4294
|
-
|
|
4295
|
-
|
|
4296
|
-
|
|
4297
|
-
|
|
4298
|
-
);
|
|
4299
|
-
}
|
|
4300
|
-
);
|
|
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(function(item) {
|
|
4240
|
+
return item.style.label.visibility === "hover" && !this.classList.contains("label-selection-pinned");
|
|
4241
|
+
}).interrupt().transition().duration(200).style("opacity", 0).style("pointer-events", "none");
|
|
4242
|
+
});
|
|
4301
4243
|
}
|
|
4302
4244
|
|
|
4303
4245
|
// src/utils/resolve-tooltip-position.ts
|
|
@@ -4589,11 +4531,33 @@ function createGraph(config) {
|
|
|
4589
4531
|
let tooltipBinding = null;
|
|
4590
4532
|
let controls = null;
|
|
4591
4533
|
let legendCleanup = null;
|
|
4534
|
+
let fitViewTimer = null;
|
|
4592
4535
|
let dimensions = { width: 0, height: 0 };
|
|
4593
4536
|
let rootGroup = null;
|
|
4594
4537
|
let svgElement = null;
|
|
4595
4538
|
let zoomBehavior = null;
|
|
4596
4539
|
let simulation = null;
|
|
4540
|
+
const nodeSelectHandlers = /* @__PURE__ */ new Set();
|
|
4541
|
+
const linkSelectHandlers = /* @__PURE__ */ new Set();
|
|
4542
|
+
function on(event, handler) {
|
|
4543
|
+
if (event === "nodeSelect") {
|
|
4544
|
+
nodeSelectHandlers.add(handler);
|
|
4545
|
+
return () => {
|
|
4546
|
+
nodeSelectHandlers.delete(handler);
|
|
4547
|
+
};
|
|
4548
|
+
}
|
|
4549
|
+
linkSelectHandlers.add(handler);
|
|
4550
|
+
return () => {
|
|
4551
|
+
linkSelectHandlers.delete(handler);
|
|
4552
|
+
};
|
|
4553
|
+
}
|
|
4554
|
+
function off(event, handler) {
|
|
4555
|
+
if (event === "nodeSelect") {
|
|
4556
|
+
nodeSelectHandlers.delete(handler);
|
|
4557
|
+
} else {
|
|
4558
|
+
linkSelectHandlers.delete(handler);
|
|
4559
|
+
}
|
|
4560
|
+
}
|
|
4597
4561
|
function render() {
|
|
4598
4562
|
destroy();
|
|
4599
4563
|
const layers = createGraphLayers(config.container);
|
|
@@ -4605,9 +4569,17 @@ function createGraph(config) {
|
|
|
4605
4569
|
layers.svg.setAttribute("height", String(height));
|
|
4606
4570
|
layers.interactionRect.setAttribute("width", String(width));
|
|
4607
4571
|
layers.interactionRect.setAttribute("height", String(height));
|
|
4608
|
-
|
|
4609
|
-
|
|
4610
|
-
|
|
4572
|
+
if (simulation) {
|
|
4573
|
+
simulation.force("center", center_default(width / 2, height / 2));
|
|
4574
|
+
simulation.alpha(0.3).restart();
|
|
4575
|
+
}
|
|
4576
|
+
if (fitViewTimer) {
|
|
4577
|
+
clearTimeout(fitViewTimer);
|
|
4578
|
+
}
|
|
4579
|
+
fitViewTimer = setTimeout(() => {
|
|
4580
|
+
fitView();
|
|
4581
|
+
fitViewTimer = null;
|
|
4582
|
+
}, 150);
|
|
4611
4583
|
});
|
|
4612
4584
|
const zoomResult = createZoom({
|
|
4613
4585
|
svg: layers.svg,
|
|
@@ -4625,8 +4597,9 @@ function createGraph(config) {
|
|
|
4625
4597
|
const simulationConfig = {
|
|
4626
4598
|
nodes: config.nodes,
|
|
4627
4599
|
links: config.links,
|
|
4628
|
-
|
|
4629
|
-
|
|
4600
|
+
// Uses the observed dimensions to ensure physics are calculated on actual container size
|
|
4601
|
+
width: dimensions.width || config.container.clientWidth,
|
|
4602
|
+
height: dimensions.height || config.container.clientHeight
|
|
4630
4603
|
};
|
|
4631
4604
|
const simulationResult = createGraphSimulation(simulationConfig);
|
|
4632
4605
|
simulation = simulationResult.simulation;
|
|
@@ -4668,10 +4641,135 @@ function createGraph(config) {
|
|
|
4668
4641
|
if (config.interaction?.drag?.enabled !== false) {
|
|
4669
4642
|
nodeSelection.call(createDragBehavior(simulation));
|
|
4670
4643
|
}
|
|
4644
|
+
const selectionConfig = config.interaction?.selection;
|
|
4645
|
+
if (selectionConfig?.enabled) {
|
|
4646
|
+
let selectedNodeElement = null;
|
|
4647
|
+
let selectedLinkElement = null;
|
|
4648
|
+
const linkMarkerSnapshots = /* @__PURE__ */ new Map();
|
|
4649
|
+
linkSelection.each(function() {
|
|
4650
|
+
const linkElement = this;
|
|
4651
|
+
linkMarkerSnapshots.set(linkElement, linkElement.getAttribute("marker-end"));
|
|
4652
|
+
});
|
|
4653
|
+
const deselectNode = () => {
|
|
4654
|
+
if (!selectedNodeElement) {
|
|
4655
|
+
return;
|
|
4656
|
+
}
|
|
4657
|
+
const nodeElement = selectedNodeElement;
|
|
4658
|
+
nodeElement.style.fill = "";
|
|
4659
|
+
nodeElement.style.stroke = "";
|
|
4660
|
+
nodeElement.style.strokeWidth = "";
|
|
4661
|
+
nodeElement.style.opacity = "";
|
|
4662
|
+
nodeElement.style.removeProperty("r");
|
|
4663
|
+
root2.selectAll(".link-label.label-selection-pinned").classed("label-selection-pinned", false).interrupt().transition().duration(200).style("opacity", 0).style("pointer-events", "none");
|
|
4664
|
+
selectedNodeElement = null;
|
|
4665
|
+
};
|
|
4666
|
+
const deselectLink = () => {
|
|
4667
|
+
if (!selectedLinkElement) {
|
|
4668
|
+
return;
|
|
4669
|
+
}
|
|
4670
|
+
const linkElement = selectedLinkElement;
|
|
4671
|
+
linkElement.style.stroke = "";
|
|
4672
|
+
linkElement.style.strokeWidth = "";
|
|
4673
|
+
linkElement.style.opacity = "";
|
|
4674
|
+
const originalMarkerEnd = linkMarkerSnapshots.get(linkElement);
|
|
4675
|
+
if (originalMarkerEnd) {
|
|
4676
|
+
linkElement.setAttribute("marker-end", originalMarkerEnd);
|
|
4677
|
+
} else {
|
|
4678
|
+
linkElement.removeAttribute("marker-end");
|
|
4679
|
+
}
|
|
4680
|
+
selectedLinkElement = null;
|
|
4681
|
+
};
|
|
4682
|
+
nodeSelection.on("click.select", function(event, node) {
|
|
4683
|
+
event.stopPropagation();
|
|
4684
|
+
const nodeElement = this;
|
|
4685
|
+
if (selectedNodeElement === nodeElement) {
|
|
4686
|
+
deselectNode();
|
|
4687
|
+
return;
|
|
4688
|
+
}
|
|
4689
|
+
deselectNode();
|
|
4690
|
+
deselectLink();
|
|
4691
|
+
selectedNodeElement = nodeElement;
|
|
4692
|
+
const nodeStyle = selectionConfig.nodeStyle;
|
|
4693
|
+
if (nodeStyle) {
|
|
4694
|
+
if (nodeStyle.fill !== void 0) {
|
|
4695
|
+
nodeElement.style.fill = nodeStyle.fill;
|
|
4696
|
+
}
|
|
4697
|
+
if (nodeStyle.stroke !== void 0) {
|
|
4698
|
+
nodeElement.style.stroke = nodeStyle.stroke;
|
|
4699
|
+
}
|
|
4700
|
+
if (nodeStyle.strokeWidth !== void 0) {
|
|
4701
|
+
nodeElement.style.strokeWidth = String(nodeStyle.strokeWidth);
|
|
4702
|
+
}
|
|
4703
|
+
if (nodeStyle.opacity !== void 0) {
|
|
4704
|
+
nodeElement.style.opacity = String(nodeStyle.opacity);
|
|
4705
|
+
}
|
|
4706
|
+
if (nodeStyle.radius !== void 0) {
|
|
4707
|
+
nodeElement.style.setProperty("r", String(nodeStyle.radius));
|
|
4708
|
+
}
|
|
4709
|
+
}
|
|
4710
|
+
root2.selectAll(".link-label").filter((item) => {
|
|
4711
|
+
if (item.style.label.visibility !== "hover") {
|
|
4712
|
+
return false;
|
|
4713
|
+
}
|
|
4714
|
+
const source = item.link.source;
|
|
4715
|
+
const target = item.link.target;
|
|
4716
|
+
return source.id === node.id || target.id === node.id;
|
|
4717
|
+
}).classed("label-selection-pinned", true).interrupt().transition().duration(200).style("opacity", 1).style("pointer-events", "auto");
|
|
4718
|
+
nodeSelectHandlers.forEach((handler) => handler(node, nodeElement));
|
|
4719
|
+
});
|
|
4720
|
+
const selectLink = (event, renderableLink, linkElement) => {
|
|
4721
|
+
event.stopPropagation();
|
|
4722
|
+
if (selectedLinkElement === linkElement) {
|
|
4723
|
+
deselectLink();
|
|
4724
|
+
return;
|
|
4725
|
+
}
|
|
4726
|
+
deselectLink();
|
|
4727
|
+
deselectNode();
|
|
4728
|
+
selectedLinkElement = linkElement;
|
|
4729
|
+
const linkStyle = selectionConfig.linkStyle;
|
|
4730
|
+
if (linkStyle) {
|
|
4731
|
+
if (linkStyle.stroke !== void 0) {
|
|
4732
|
+
linkElement.style.stroke = linkStyle.stroke;
|
|
4733
|
+
}
|
|
4734
|
+
if (linkStyle.strokeWidth !== void 0) {
|
|
4735
|
+
linkElement.style.strokeWidth = String(linkStyle.strokeWidth);
|
|
4736
|
+
}
|
|
4737
|
+
if (linkStyle.opacity !== void 0) {
|
|
4738
|
+
linkElement.style.opacity = String(linkStyle.opacity);
|
|
4739
|
+
}
|
|
4740
|
+
if (linkStyle.stroke !== void 0 && renderableLink.style.arrow.enabled) {
|
|
4741
|
+
const selectionMarkerStyle = {
|
|
4742
|
+
stroke: linkStyle.stroke,
|
|
4743
|
+
arrow: { fill: linkStyle.stroke, size: renderableLink.style.arrow.size }
|
|
4744
|
+
};
|
|
4745
|
+
const selectionMarkerId = createArrowMarker({ svg: layers.svg, style: selectionMarkerStyle });
|
|
4746
|
+
select_default2(linkElement).attr("marker-end", `url(#${selectionMarkerId})`);
|
|
4747
|
+
}
|
|
4748
|
+
}
|
|
4749
|
+
linkSelectHandlers.forEach((handler) => handler(renderableLink.link, linkElement));
|
|
4750
|
+
};
|
|
4751
|
+
linkSelection.on("click.select", function(event, renderableLink) {
|
|
4752
|
+
selectLink(event, renderableLink, this);
|
|
4753
|
+
});
|
|
4754
|
+
const linkHitAreaSelection = root2.select('[data-layer="links"]').selectAll("line.link-hit-area").data(linkSelection.data()).join("line").attr("class", "link-hit-area").attr("stroke", "rgba(0,0,0,0)").attr("stroke-width", (item) => item.style.arrow.size * 4).style("pointer-events", "stroke").style("cursor", "pointer").attr("opacity", 0);
|
|
4755
|
+
simulation.on("tick.hitarea", () => {
|
|
4756
|
+
linkHitAreaSelection.attr("x1", (item) => item.link.source.x ?? 0).attr("y1", (item) => item.link.source.y ?? 0).attr("x2", (item) => item.link.target.x ?? 0).attr("y2", (item) => item.link.target.y ?? 0);
|
|
4757
|
+
});
|
|
4758
|
+
linkHitAreaSelection.on("click.select", function(event, renderableLink) {
|
|
4759
|
+
const visibleLinkNode = linkSelection.filter((d) => d === renderableLink).node();
|
|
4760
|
+
if (visibleLinkNode) {
|
|
4761
|
+
selectLink(event, renderableLink, visibleLinkNode);
|
|
4762
|
+
}
|
|
4763
|
+
});
|
|
4764
|
+
select_default2(layers.svg).on("click.deselect", () => {
|
|
4765
|
+
deselectNode();
|
|
4766
|
+
deselectLink();
|
|
4767
|
+
});
|
|
4768
|
+
}
|
|
4671
4769
|
if (config.controls?.enabled) {
|
|
4672
4770
|
controls = createGraphControls(
|
|
4673
4771
|
layers.overlay,
|
|
4674
|
-
{ zoomIn, zoomOut, resetView, fitView
|
|
4772
|
+
{ zoomIn, zoomOut, resetView, fitView },
|
|
4675
4773
|
config.controls
|
|
4676
4774
|
);
|
|
4677
4775
|
controls.mount();
|
|
@@ -4681,25 +4779,35 @@ function createGraph(config) {
|
|
|
4681
4779
|
}
|
|
4682
4780
|
}
|
|
4683
4781
|
function resetView() {
|
|
4684
|
-
if (!zoomBehavior || !svgElement)
|
|
4685
|
-
|
|
4782
|
+
if (!zoomBehavior || !svgElement) {
|
|
4783
|
+
return;
|
|
4784
|
+
}
|
|
4785
|
+
select_default2(svgElement).transition().duration(400).call(zoomBehavior.transform, identity2);
|
|
4686
4786
|
}
|
|
4687
4787
|
function fitView() {
|
|
4688
|
-
if (!zoomBehavior || !rootGroup || !svgElement || dimensions.width === 0 || dimensions.height === 0)
|
|
4788
|
+
if (!zoomBehavior || !rootGroup || !svgElement || dimensions.width === 0 || dimensions.height === 0) {
|
|
4789
|
+
return;
|
|
4790
|
+
}
|
|
4689
4791
|
const bounds = rootGroup.getBBox();
|
|
4690
|
-
if (bounds.width === 0 || bounds.height === 0)
|
|
4792
|
+
if (bounds.width === 0 || bounds.height === 0) {
|
|
4793
|
+
return;
|
|
4794
|
+
}
|
|
4691
4795
|
const scale = Math.min(dimensions.width / bounds.width, dimensions.height / bounds.height) * 0.9;
|
|
4692
4796
|
const translateX = (dimensions.width - bounds.width * scale) / 2 - bounds.x * scale;
|
|
4693
4797
|
const translateY = (dimensions.height - bounds.height * scale) / 2 - bounds.y * scale;
|
|
4694
4798
|
const transform2 = identity2.translate(translateX, translateY).scale(scale);
|
|
4695
|
-
select_default2(svgElement).transition().call(zoomBehavior.transform, transform2);
|
|
4799
|
+
select_default2(svgElement).transition().duration(400).call(zoomBehavior.transform, transform2);
|
|
4696
4800
|
}
|
|
4697
4801
|
function zoomIn() {
|
|
4698
|
-
if (!zoomBehavior || !svgElement)
|
|
4802
|
+
if (!zoomBehavior || !svgElement) {
|
|
4803
|
+
return;
|
|
4804
|
+
}
|
|
4699
4805
|
select_default2(svgElement).transition().call(zoomBehavior.scaleBy, 1.2);
|
|
4700
4806
|
}
|
|
4701
4807
|
function zoomOut() {
|
|
4702
|
-
if (!zoomBehavior || !svgElement)
|
|
4808
|
+
if (!zoomBehavior || !svgElement) {
|
|
4809
|
+
return;
|
|
4810
|
+
}
|
|
4703
4811
|
select_default2(svgElement).transition().call(zoomBehavior.scaleBy, 0.8);
|
|
4704
4812
|
}
|
|
4705
4813
|
async function exportGraph(fileName) {
|
|
@@ -4711,6 +4819,10 @@ function createGraph(config) {
|
|
|
4711
4819
|
});
|
|
4712
4820
|
}
|
|
4713
4821
|
function destroy() {
|
|
4822
|
+
if (fitViewTimer) {
|
|
4823
|
+
clearTimeout(fitViewTimer);
|
|
4824
|
+
fitViewTimer = null;
|
|
4825
|
+
}
|
|
4714
4826
|
if (cleanupResize) {
|
|
4715
4827
|
cleanupResize();
|
|
4716
4828
|
cleanupResize = null;
|
|
@@ -4742,7 +4854,7 @@ function createGraph(config) {
|
|
|
4742
4854
|
config.container.removeChild(config.container.firstChild);
|
|
4743
4855
|
}
|
|
4744
4856
|
}
|
|
4745
|
-
return { render, zoomIn, zoomOut, resetView, fitView, destroy, exportGraph };
|
|
4857
|
+
return { render, zoomIn, zoomOut, resetView, fitView, destroy, exportGraph, on, off };
|
|
4746
4858
|
}
|
|
4747
4859
|
// Annotate the CommonJS export names for ESM import in node:
|
|
4748
4860
|
0 && (module.exports = {
|
package/dist/index.d.cts
CHANGED
|
@@ -66,6 +66,7 @@ interface LinkArrowStyle {
|
|
|
66
66
|
}
|
|
67
67
|
interface LinkLabelStyle {
|
|
68
68
|
readonly enabled?: boolean;
|
|
69
|
+
readonly visibility?: 'always' | 'hover' | 'selection';
|
|
69
70
|
readonly backgroundFill?: string;
|
|
70
71
|
readonly borderColor?: string;
|
|
71
72
|
readonly borderWidth?: number;
|
|
@@ -117,6 +118,13 @@ interface GraphConfig {
|
|
|
117
118
|
readonly container: HTMLElement;
|
|
118
119
|
readonly nodes: GraphNode[];
|
|
119
120
|
readonly links: GraphLink[];
|
|
121
|
+
readonly autoFit?: boolean;
|
|
122
|
+
readonly responsive?: boolean;
|
|
123
|
+
readonly simulation?: {
|
|
124
|
+
readonly alpha?: number;
|
|
125
|
+
readonly gravity?: number;
|
|
126
|
+
readonly linkDistance?: number | ((link: GraphLink) => number);
|
|
127
|
+
};
|
|
120
128
|
readonly interaction?: GraphInteractionConfig;
|
|
121
129
|
readonly controls?: GraphControlsConfig;
|
|
122
130
|
readonly legend?: LegendConfig;
|
|
@@ -130,6 +138,10 @@ interface GraphInstance {
|
|
|
130
138
|
fitView(): void;
|
|
131
139
|
destroy(): void;
|
|
132
140
|
exportGraph(fileName?: string): void;
|
|
141
|
+
on(event: 'nodeSelect', handler: (node: GraphNode, element: SVGCircleElement) => void): () => void;
|
|
142
|
+
on(event: 'linkSelect', handler: (link: GraphLink, element: SVGLineElement) => void): () => void;
|
|
143
|
+
off(event: 'nodeSelect', handler: (node: GraphNode, element: SVGCircleElement) => void): void;
|
|
144
|
+
off(event: 'linkSelect', handler: (link: GraphLink, element: SVGLineElement) => void): void;
|
|
133
145
|
}
|
|
134
146
|
|
|
135
147
|
declare function createGraph(config: GraphConfig): GraphInstance;
|
package/dist/index.d.ts
CHANGED
|
@@ -66,6 +66,7 @@ interface LinkArrowStyle {
|
|
|
66
66
|
}
|
|
67
67
|
interface LinkLabelStyle {
|
|
68
68
|
readonly enabled?: boolean;
|
|
69
|
+
readonly visibility?: 'always' | 'hover' | 'selection';
|
|
69
70
|
readonly backgroundFill?: string;
|
|
70
71
|
readonly borderColor?: string;
|
|
71
72
|
readonly borderWidth?: number;
|
|
@@ -117,6 +118,13 @@ interface GraphConfig {
|
|
|
117
118
|
readonly container: HTMLElement;
|
|
118
119
|
readonly nodes: GraphNode[];
|
|
119
120
|
readonly links: GraphLink[];
|
|
121
|
+
readonly autoFit?: boolean;
|
|
122
|
+
readonly responsive?: boolean;
|
|
123
|
+
readonly simulation?: {
|
|
124
|
+
readonly alpha?: number;
|
|
125
|
+
readonly gravity?: number;
|
|
126
|
+
readonly linkDistance?: number | ((link: GraphLink) => number);
|
|
127
|
+
};
|
|
120
128
|
readonly interaction?: GraphInteractionConfig;
|
|
121
129
|
readonly controls?: GraphControlsConfig;
|
|
122
130
|
readonly legend?: LegendConfig;
|
|
@@ -130,6 +138,10 @@ interface GraphInstance {
|
|
|
130
138
|
fitView(): void;
|
|
131
139
|
destroy(): void;
|
|
132
140
|
exportGraph(fileName?: string): void;
|
|
141
|
+
on(event: 'nodeSelect', handler: (node: GraphNode, element: SVGCircleElement) => void): () => void;
|
|
142
|
+
on(event: 'linkSelect', handler: (link: GraphLink, element: SVGLineElement) => void): () => void;
|
|
143
|
+
off(event: 'nodeSelect', handler: (node: GraphNode, element: SVGCircleElement) => void): void;
|
|
144
|
+
off(event: 'linkSelect', handler: (link: GraphLink, element: SVGLineElement) => void): void;
|
|
133
145
|
}
|
|
134
146
|
|
|
135
147
|
declare function createGraph(config: GraphConfig): GraphInstance;
|