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/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(150).strength(0.4)
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).strength(0.9)
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
- return ctx.root.select('[data-layer="links"]').selectAll("line").data(renderableLinks, (item) => getLinkKey(item.link)).join("line").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);
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) => ({ link, style: resolveLinkStyle({ link, interaction: params.interaction }) })
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").attr("pointer-events", "auto").attr("cursor", "pointer");
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
- if (!hoverStyle) {
4250
- return;
4251
- }
4252
- nodeSelection.on(
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
- const hoverStroke = hoverStyle.stroke ?? node.style?.stroke ?? "#ffffff";
4257
- const hoverStrokeWidth = hoverStyle.strokeWidth ?? node.style?.strokeWidth ?? 1.5;
4258
- const hoverOpacity = hoverStyle.opacity ?? node.style?.opacity ?? 1;
4259
- circle.setAttribute(
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
- const defaultStroke = node.style?.stroke ?? "#ffffff";
4281
- const defaultStrokeWidth = node.style?.strokeWidth ?? 1.5;
4282
- const defaultOpacity = node.style?.opacity ?? 1;
4283
- circle.setAttribute(
4284
- "stroke",
4285
- defaultStroke
4286
- );
4287
- circle.setAttribute(
4288
- "stroke-width",
4289
- String(
4290
- defaultStrokeWidth
4291
- )
4292
- );
4293
- circle.setAttribute(
4294
- "opacity",
4295
- String(
4296
- defaultOpacity
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
- simulation?.force("x", x_default2(width / 2).strength(0.03));
4609
- simulation?.force("y", y_default2(height / 2).strength(0.03));
4610
- simulation?.alpha(0.25).restart();
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
- width: config.container.clientWidth,
4629
- height: config.container.clientHeight
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, destroy, render, exportGraph },
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) return;
4685
- select_default2(svgElement).transition().call(zoomBehavior.transform, identity2);
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) return;
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) return;
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) return;
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) return;
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;