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.js CHANGED
@@ -3608,68 +3608,6 @@ function manyBody_default() {
3608
3608
  return force;
3609
3609
  }
3610
3610
 
3611
- // node_modules/d3-force/src/x.js
3612
- function x_default2(x3) {
3613
- var strength = constant_default5(0.1), nodes, strengths, xz;
3614
- if (typeof x3 !== "function") x3 = constant_default5(x3 == null ? 0 : +x3);
3615
- function force(alpha) {
3616
- for (var i = 0, n = nodes.length, node; i < n; ++i) {
3617
- node = nodes[i], node.vx += (xz[i] - node.x) * strengths[i] * alpha;
3618
- }
3619
- }
3620
- function initialize() {
3621
- if (!nodes) return;
3622
- var i, n = nodes.length;
3623
- strengths = new Array(n);
3624
- xz = new Array(n);
3625
- for (i = 0; i < n; ++i) {
3626
- strengths[i] = isNaN(xz[i] = +x3(nodes[i], i, nodes)) ? 0 : +strength(nodes[i], i, nodes);
3627
- }
3628
- }
3629
- force.initialize = function(_) {
3630
- nodes = _;
3631
- initialize();
3632
- };
3633
- force.strength = function(_) {
3634
- return arguments.length ? (strength = typeof _ === "function" ? _ : constant_default5(+_), initialize(), force) : strength;
3635
- };
3636
- force.x = function(_) {
3637
- return arguments.length ? (x3 = typeof _ === "function" ? _ : constant_default5(+_), initialize(), force) : x3;
3638
- };
3639
- return force;
3640
- }
3641
-
3642
- // node_modules/d3-force/src/y.js
3643
- function y_default2(y3) {
3644
- var strength = constant_default5(0.1), nodes, strengths, yz;
3645
- if (typeof y3 !== "function") y3 = constant_default5(y3 == null ? 0 : +y3);
3646
- function force(alpha) {
3647
- for (var i = 0, n = nodes.length, node; i < n; ++i) {
3648
- node = nodes[i], node.vy += (yz[i] - node.y) * strengths[i] * alpha;
3649
- }
3650
- }
3651
- function initialize() {
3652
- if (!nodes) return;
3653
- var i, n = nodes.length;
3654
- strengths = new Array(n);
3655
- yz = new Array(n);
3656
- for (i = 0; i < n; ++i) {
3657
- strengths[i] = isNaN(yz[i] = +y3(nodes[i], i, nodes)) ? 0 : +strength(nodes[i], i, nodes);
3658
- }
3659
- }
3660
- force.initialize = function(_) {
3661
- nodes = _;
3662
- initialize();
3663
- };
3664
- force.strength = function(_) {
3665
- return arguments.length ? (strength = typeof _ === "function" ? _ : constant_default5(+_), initialize(), force) : strength;
3666
- };
3667
- force.y = function(_) {
3668
- return arguments.length ? (y3 = typeof _ === "function" ? _ : constant_default5(+_), initialize(), force) : y3;
3669
- };
3670
- return force;
3671
- }
3672
-
3673
3611
  // src/core/create-graph-layers.ts
3674
3612
  function createGraphLayers(host) {
3675
3613
  host.innerHTML = "";
@@ -3778,14 +3716,71 @@ function createGraphSimulation(config) {
3778
3716
  });
3779
3717
  const simulation = simulation_default(config.nodes).alpha(0.9).alphaDecay(0.12).alphaMin(0.03).velocityDecay(0.5).force(
3780
3718
  "link",
3781
- link_default(config.links).id((d) => d.id).distance(150).strength(0.4)
3719
+ link_default(config.links).id((d) => d.id).distance((d) => {
3720
+ const source = d.source;
3721
+ const target = d.target;
3722
+ const sourceR = source.style?.radius || 20;
3723
+ const targetR = target.style?.radius || 20;
3724
+ const labelBuffer = d.style?.label?.height || 40;
3725
+ return (sourceR + targetR + labelBuffer) * 2;
3726
+ }).strength(0.8)
3782
3727
  ).force("charge", manyBody_default().strength(-220)).force(
3783
3728
  "collide",
3784
- collide_default().radius((node) => (node.style?.radius ?? 12) + 10).strength(0.9)
3729
+ collide_default().radius((node) => (node.style?.radius ?? 12) + 10).iterations(2)
3785
3730
  ).force("center", center_default(centerX, centerY).strength(0.08));
3786
3731
  return { simulation };
3787
3732
  }
3788
3733
 
3734
+ // src/utils/get-link-marker-id.ts
3735
+ function getLinkMarkerId(style) {
3736
+ const markerStyle = {
3737
+ stroke: style.stroke ?? "#94a3b8",
3738
+ strokeWidth: style.strokeWidth ?? 2,
3739
+ arrowFill: style.arrow?.fill ?? style.stroke ?? "#94a3b8",
3740
+ arrowSize: style.arrow?.size ?? 6
3741
+ };
3742
+ const serializedStyle = JSON.stringify(markerStyle);
3743
+ const hash = createHash(serializedStyle);
3744
+ return `graph-arrow-${hash}`;
3745
+ }
3746
+ function createHash(value) {
3747
+ let hash = 0;
3748
+ for (let index2 = 0; index2 < value.length; index2 += 1) {
3749
+ const charCode = value.charCodeAt(index2);
3750
+ hash = (hash << 5) - hash + charCode;
3751
+ hash |= 0;
3752
+ }
3753
+ return Math.abs(hash).toString(36);
3754
+ }
3755
+
3756
+ // src/core/create-arrow-marker.ts
3757
+ function createArrowMarker(params) {
3758
+ const markerId = getLinkMarkerId(params.style);
3759
+ const existingMarker = params.svg.querySelector(`#${markerId}`);
3760
+ if (existingMarker) {
3761
+ return markerId;
3762
+ }
3763
+ const arrowSize = params.style.arrow?.size ?? 6;
3764
+ const fill = params.style.arrow?.fill ?? params.style.stroke ?? "#94a3b8";
3765
+ const defs = document.createElementNS("http://www.w3.org/2000/svg", "defs");
3766
+ const marker = document.createElementNS("http://www.w3.org/2000/svg", "marker");
3767
+ marker.setAttribute("id", markerId);
3768
+ marker.setAttribute("viewBox", "0 0 20 20");
3769
+ marker.setAttribute("refX", "0");
3770
+ marker.setAttribute("refY", "10");
3771
+ marker.setAttribute("markerWidth", String(arrowSize * 2));
3772
+ marker.setAttribute("markerHeight", String(arrowSize * 2));
3773
+ marker.setAttribute("orient", "auto");
3774
+ marker.setAttribute("markerUnits", "userSpaceOnUse");
3775
+ const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
3776
+ path.setAttribute("d", "M 0 0 L 20 10 L 0 20 z");
3777
+ path.setAttribute("fill", fill);
3778
+ marker.appendChild(path);
3779
+ defs.appendChild(marker);
3780
+ params.svg.insertBefore(defs, params.svg.firstChild);
3781
+ return markerId;
3782
+ }
3783
+
3789
3784
  // src/controls/graph-controls.utils.ts
3790
3785
  function resolveControlsPosition(position) {
3791
3786
  return position ?? "bottom-left";
@@ -3964,6 +3959,7 @@ var DEFAULT_LINK_STYLE = {
3964
3959
  },
3965
3960
  label: {
3966
3961
  enabled: true,
3962
+ visibility: "always",
3967
3963
  backgroundFill: "color-mix(in srgb, #8E42EE, #FFFFFF 90%)",
3968
3964
  borderColor: "color-mix(in srgb, #8E42EE, #FFFFFF 10%)",
3969
3965
  borderWidth: 1.5,
@@ -3999,6 +3995,7 @@ function mergeLinkStyle(base, override) {
3999
3995
  },
4000
3996
  label: {
4001
3997
  enabled: override?.label?.enabled ?? base.label.enabled,
3998
+ visibility: override?.label?.visibility ?? base.label.visibility,
4002
3999
  backgroundFill: override?.label?.backgroundFill ?? base.label.backgroundFill,
4003
4000
  borderColor: override?.label?.borderColor ?? base.label.borderColor,
4004
4001
  borderWidth: override?.label?.borderWidth ?? base.label.borderWidth,
@@ -4012,56 +4009,6 @@ function mergeLinkStyle(base, override) {
4012
4009
  };
4013
4010
  }
4014
4011
 
4015
- // src/utils/get-link-marker-id.ts
4016
- function getLinkMarkerId(style) {
4017
- const markerStyle = {
4018
- stroke: style.stroke ?? "#94a3b8",
4019
- strokeWidth: style.strokeWidth ?? 2,
4020
- arrowFill: style.arrow?.fill ?? style.stroke ?? "#94a3b8",
4021
- arrowSize: style.arrow?.size ?? 6
4022
- };
4023
- const serializedStyle = JSON.stringify(markerStyle);
4024
- const hash = createHash(serializedStyle);
4025
- return `graph-arrow-${hash}`;
4026
- }
4027
- function createHash(value) {
4028
- let hash = 0;
4029
- for (let index2 = 0; index2 < value.length; index2 += 1) {
4030
- const charCode = value.charCodeAt(index2);
4031
- hash = (hash << 5) - hash + charCode;
4032
- hash |= 0;
4033
- }
4034
- return Math.abs(hash).toString(36);
4035
- }
4036
-
4037
- // src/core/create-arrow-marker.ts
4038
- function createArrowMarker(params) {
4039
- const markerId = getLinkMarkerId(params.style);
4040
- const existingMarker = params.svg.querySelector(`#${markerId}`);
4041
- if (existingMarker) {
4042
- return markerId;
4043
- }
4044
- const arrowSize = params.style.arrow?.size ?? 6;
4045
- const fill = params.style.arrow?.fill ?? params.style.stroke ?? "#94a3b8";
4046
- const defs = document.createElementNS("http://www.w3.org/2000/svg", "defs");
4047
- const marker = document.createElementNS("http://www.w3.org/2000/svg", "marker");
4048
- marker.setAttribute("id", markerId);
4049
- marker.setAttribute("viewBox", "0 0 20 20");
4050
- marker.setAttribute("refX", "0");
4051
- marker.setAttribute("refY", "10");
4052
- marker.setAttribute("markerWidth", String(arrowSize * 2));
4053
- marker.setAttribute("markerHeight", String(arrowSize * 2));
4054
- marker.setAttribute("orient", "auto");
4055
- marker.setAttribute("markerUnits", "userSpaceOnUse");
4056
- const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
4057
- path.setAttribute("d", "M 0 0 L 20 10 L 0 20 z");
4058
- path.setAttribute("fill", fill);
4059
- marker.appendChild(path);
4060
- defs.appendChild(marker);
4061
- params.svg.insertBefore(defs, params.svg.firstChild);
4062
- return markerId;
4063
- }
4064
-
4065
4012
  // src/renderer/links.ts
4066
4013
  function getShortenedTargetPoint(link, style) {
4067
4014
  const source = link.source;
@@ -4099,7 +4046,14 @@ function getLinkKey(link) {
4099
4046
  }
4100
4047
  function renderLinks(ctx, links) {
4101
4048
  const renderableLinks = createRenderableLinks(ctx, links);
4102
- 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);
4049
+ 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");
4050
+ const labelSelection = ctx.root.selectAll(".link-label");
4051
+ linkSelection.on("mouseenter.label-hover", (_event, d) => {
4052
+ labelSelection.filter((labelItem) => labelItem.link === d.link && labelItem.style.label.visibility === "hover").interrupt().transition().duration(200).style("opacity", 1);
4053
+ }).on("mouseleave.label-hover", (_event, d) => {
4054
+ labelSelection.filter((labelItem) => labelItem.link === d.link && labelItem.style.label.visibility === "hover").interrupt().transition().duration(200).style("opacity", 0);
4055
+ });
4056
+ return linkSelection;
4103
4057
  }
4104
4058
 
4105
4059
  // src/renderer/nodes.ts
@@ -4169,7 +4123,10 @@ function renderNodeLabels(ctx, nodes) {
4169
4123
  // src/renderer/link-labels.ts
4170
4124
  function createRenderableLinks2(params, links) {
4171
4125
  return links.map(
4172
- (link) => ({ link, style: resolveLinkStyle({ link, interaction: params.interaction }) })
4126
+ (link) => ({
4127
+ link,
4128
+ style: resolveLinkStyle({ link, interaction: params.interaction })
4129
+ })
4173
4130
  ).filter(
4174
4131
  (item) => item.style.label.enabled && Boolean(item.link.label)
4175
4132
  );
@@ -4181,7 +4138,13 @@ function getLinkKey2(link) {
4181
4138
  }
4182
4139
  function renderLinkLabels(params, links) {
4183
4140
  const renderableLinks = createRenderableLinks2(params, links);
4184
- const labelSelection = params.root.select('[data-layer="link-labels"]').selectAll(".link-label").data(renderableLinks, (item) => getLinkKey2(item.link)).join("g").attr("class", "link-label").attr("pointer-events", "auto").attr("cursor", "pointer");
4141
+ 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) => {
4142
+ const visibility = item.style.label.visibility ?? "always";
4143
+ return visibility === "always" ? 1 : 0;
4144
+ }).style("pointer-events", (item) => {
4145
+ const visibility = item.style.label.visibility ?? "always";
4146
+ return visibility === "always" ? "auto" : "none";
4147
+ }).style("cursor", "pointer");
4185
4148
  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);
4186
4149
  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 ?? "");
4187
4150
  return labelSelection;
@@ -4210,58 +4173,37 @@ function createDragBehavior(simulation) {
4210
4173
 
4211
4174
  // src/interactions/create-node-hover.ts
4212
4175
  function createNodeHover(nodeSelection, hoverStyle) {
4213
- if (!hoverStyle) {
4214
- return;
4215
- }
4216
- nodeSelection.on(
4217
- "mouseenter.hover",
4218
- function(_event, node) {
4176
+ const firstNode = nodeSelection.node();
4177
+ if (!firstNode) return;
4178
+ if (hoverStyle) {
4179
+ nodeSelection.on("mouseenter.hover", function(_event, node) {
4219
4180
  const circle = this;
4220
- const hoverStroke = hoverStyle.stroke ?? node.style?.stroke ?? "#ffffff";
4221
- const hoverStrokeWidth = hoverStyle.strokeWidth ?? node.style?.strokeWidth ?? 1.5;
4222
- const hoverOpacity = hoverStyle.opacity ?? node.style?.opacity ?? 1;
4223
- circle.setAttribute(
4224
- "stroke",
4225
- hoverStroke
4226
- );
4227
- circle.setAttribute(
4228
- "stroke-width",
4229
- String(
4230
- hoverStrokeWidth
4231
- )
4232
- );
4233
- circle.setAttribute(
4234
- "opacity",
4235
- String(
4236
- hoverOpacity
4237
- )
4238
- );
4239
- }
4240
- ).on(
4241
- "mouseleave.hover",
4242
- function(_event, node) {
4181
+ circle.setAttribute("stroke", hoverStyle.stroke ?? node.style?.stroke ?? "#ffffff");
4182
+ circle.setAttribute("stroke-width", String(hoverStyle.strokeWidth ?? node.style?.strokeWidth ?? 1.5));
4183
+ circle.setAttribute("opacity", String(hoverStyle.opacity ?? node.style?.opacity ?? 1));
4184
+ }).on("mouseleave.hover", function(_event, node) {
4243
4185
  const circle = this;
4244
- const defaultStroke = node.style?.stroke ?? "#ffffff";
4245
- const defaultStrokeWidth = node.style?.strokeWidth ?? 1.5;
4246
- const defaultOpacity = node.style?.opacity ?? 1;
4247
- circle.setAttribute(
4248
- "stroke",
4249
- defaultStroke
4250
- );
4251
- circle.setAttribute(
4252
- "stroke-width",
4253
- String(
4254
- defaultStrokeWidth
4255
- )
4256
- );
4257
- circle.setAttribute(
4258
- "opacity",
4259
- String(
4260
- defaultOpacity
4261
- )
4262
- );
4263
- }
4264
- );
4186
+ circle.setAttribute("stroke", node.style?.stroke ?? "#ffffff");
4187
+ circle.setAttribute("stroke-width", String(node.style?.strokeWidth ?? 1.5));
4188
+ circle.setAttribute("opacity", String(node.style?.opacity ?? 1));
4189
+ });
4190
+ }
4191
+ const svgElement = firstNode.ownerSVGElement;
4192
+ if (!svgElement) return;
4193
+ const root2 = select_default2(svgElement);
4194
+ const labelSelection = root2.selectAll(".link-label");
4195
+ nodeSelection.on("mouseenter.labels", (_event, d) => {
4196
+ labelSelection.filter((item) => {
4197
+ if (item.style.label.visibility !== "hover") return false;
4198
+ const s = item.link.source;
4199
+ const t = item.link.target;
4200
+ return s.id === d.id || t.id === d.id;
4201
+ }).interrupt().transition().duration(200).style("opacity", 1).style("pointer-events", "auto");
4202
+ }).on("mouseleave.labels", (_event) => {
4203
+ labelSelection.filter(function(item) {
4204
+ return item.style.label.visibility === "hover" && !this.classList.contains("label-selection-pinned");
4205
+ }).interrupt().transition().duration(200).style("opacity", 0).style("pointer-events", "none");
4206
+ });
4265
4207
  }
4266
4208
 
4267
4209
  // src/utils/resolve-tooltip-position.ts
@@ -4553,11 +4495,33 @@ function createGraph(config) {
4553
4495
  let tooltipBinding = null;
4554
4496
  let controls = null;
4555
4497
  let legendCleanup = null;
4498
+ let fitViewTimer = null;
4556
4499
  let dimensions = { width: 0, height: 0 };
4557
4500
  let rootGroup = null;
4558
4501
  let svgElement = null;
4559
4502
  let zoomBehavior = null;
4560
4503
  let simulation = null;
4504
+ const nodeSelectHandlers = /* @__PURE__ */ new Set();
4505
+ const linkSelectHandlers = /* @__PURE__ */ new Set();
4506
+ function on(event, handler) {
4507
+ if (event === "nodeSelect") {
4508
+ nodeSelectHandlers.add(handler);
4509
+ return () => {
4510
+ nodeSelectHandlers.delete(handler);
4511
+ };
4512
+ }
4513
+ linkSelectHandlers.add(handler);
4514
+ return () => {
4515
+ linkSelectHandlers.delete(handler);
4516
+ };
4517
+ }
4518
+ function off(event, handler) {
4519
+ if (event === "nodeSelect") {
4520
+ nodeSelectHandlers.delete(handler);
4521
+ } else {
4522
+ linkSelectHandlers.delete(handler);
4523
+ }
4524
+ }
4561
4525
  function render() {
4562
4526
  destroy();
4563
4527
  const layers = createGraphLayers(config.container);
@@ -4569,9 +4533,17 @@ function createGraph(config) {
4569
4533
  layers.svg.setAttribute("height", String(height));
4570
4534
  layers.interactionRect.setAttribute("width", String(width));
4571
4535
  layers.interactionRect.setAttribute("height", String(height));
4572
- simulation?.force("x", x_default2(width / 2).strength(0.03));
4573
- simulation?.force("y", y_default2(height / 2).strength(0.03));
4574
- simulation?.alpha(0.25).restart();
4536
+ if (simulation) {
4537
+ simulation.force("center", center_default(width / 2, height / 2));
4538
+ simulation.alpha(0.3).restart();
4539
+ }
4540
+ if (fitViewTimer) {
4541
+ clearTimeout(fitViewTimer);
4542
+ }
4543
+ fitViewTimer = setTimeout(() => {
4544
+ fitView();
4545
+ fitViewTimer = null;
4546
+ }, 150);
4575
4547
  });
4576
4548
  const zoomResult = createZoom({
4577
4549
  svg: layers.svg,
@@ -4589,8 +4561,9 @@ function createGraph(config) {
4589
4561
  const simulationConfig = {
4590
4562
  nodes: config.nodes,
4591
4563
  links: config.links,
4592
- width: config.container.clientWidth,
4593
- height: config.container.clientHeight
4564
+ // Uses the observed dimensions to ensure physics are calculated on actual container size
4565
+ width: dimensions.width || config.container.clientWidth,
4566
+ height: dimensions.height || config.container.clientHeight
4594
4567
  };
4595
4568
  const simulationResult = createGraphSimulation(simulationConfig);
4596
4569
  simulation = simulationResult.simulation;
@@ -4632,10 +4605,135 @@ function createGraph(config) {
4632
4605
  if (config.interaction?.drag?.enabled !== false) {
4633
4606
  nodeSelection.call(createDragBehavior(simulation));
4634
4607
  }
4608
+ const selectionConfig = config.interaction?.selection;
4609
+ if (selectionConfig?.enabled) {
4610
+ let selectedNodeElement = null;
4611
+ let selectedLinkElement = null;
4612
+ const linkMarkerSnapshots = /* @__PURE__ */ new Map();
4613
+ linkSelection.each(function() {
4614
+ const linkElement = this;
4615
+ linkMarkerSnapshots.set(linkElement, linkElement.getAttribute("marker-end"));
4616
+ });
4617
+ const deselectNode = () => {
4618
+ if (!selectedNodeElement) {
4619
+ return;
4620
+ }
4621
+ const nodeElement = selectedNodeElement;
4622
+ nodeElement.style.fill = "";
4623
+ nodeElement.style.stroke = "";
4624
+ nodeElement.style.strokeWidth = "";
4625
+ nodeElement.style.opacity = "";
4626
+ nodeElement.style.removeProperty("r");
4627
+ root2.selectAll(".link-label.label-selection-pinned").classed("label-selection-pinned", false).interrupt().transition().duration(200).style("opacity", 0).style("pointer-events", "none");
4628
+ selectedNodeElement = null;
4629
+ };
4630
+ const deselectLink = () => {
4631
+ if (!selectedLinkElement) {
4632
+ return;
4633
+ }
4634
+ const linkElement = selectedLinkElement;
4635
+ linkElement.style.stroke = "";
4636
+ linkElement.style.strokeWidth = "";
4637
+ linkElement.style.opacity = "";
4638
+ const originalMarkerEnd = linkMarkerSnapshots.get(linkElement);
4639
+ if (originalMarkerEnd) {
4640
+ linkElement.setAttribute("marker-end", originalMarkerEnd);
4641
+ } else {
4642
+ linkElement.removeAttribute("marker-end");
4643
+ }
4644
+ selectedLinkElement = null;
4645
+ };
4646
+ nodeSelection.on("click.select", function(event, node) {
4647
+ event.stopPropagation();
4648
+ const nodeElement = this;
4649
+ if (selectedNodeElement === nodeElement) {
4650
+ deselectNode();
4651
+ return;
4652
+ }
4653
+ deselectNode();
4654
+ deselectLink();
4655
+ selectedNodeElement = nodeElement;
4656
+ const nodeStyle = selectionConfig.nodeStyle;
4657
+ if (nodeStyle) {
4658
+ if (nodeStyle.fill !== void 0) {
4659
+ nodeElement.style.fill = nodeStyle.fill;
4660
+ }
4661
+ if (nodeStyle.stroke !== void 0) {
4662
+ nodeElement.style.stroke = nodeStyle.stroke;
4663
+ }
4664
+ if (nodeStyle.strokeWidth !== void 0) {
4665
+ nodeElement.style.strokeWidth = String(nodeStyle.strokeWidth);
4666
+ }
4667
+ if (nodeStyle.opacity !== void 0) {
4668
+ nodeElement.style.opacity = String(nodeStyle.opacity);
4669
+ }
4670
+ if (nodeStyle.radius !== void 0) {
4671
+ nodeElement.style.setProperty("r", String(nodeStyle.radius));
4672
+ }
4673
+ }
4674
+ root2.selectAll(".link-label").filter((item) => {
4675
+ if (item.style.label.visibility !== "hover") {
4676
+ return false;
4677
+ }
4678
+ const source = item.link.source;
4679
+ const target = item.link.target;
4680
+ return source.id === node.id || target.id === node.id;
4681
+ }).classed("label-selection-pinned", true).interrupt().transition().duration(200).style("opacity", 1).style("pointer-events", "auto");
4682
+ nodeSelectHandlers.forEach((handler) => handler(node, nodeElement));
4683
+ });
4684
+ const selectLink = (event, renderableLink, linkElement) => {
4685
+ event.stopPropagation();
4686
+ if (selectedLinkElement === linkElement) {
4687
+ deselectLink();
4688
+ return;
4689
+ }
4690
+ deselectLink();
4691
+ deselectNode();
4692
+ selectedLinkElement = linkElement;
4693
+ const linkStyle = selectionConfig.linkStyle;
4694
+ if (linkStyle) {
4695
+ if (linkStyle.stroke !== void 0) {
4696
+ linkElement.style.stroke = linkStyle.stroke;
4697
+ }
4698
+ if (linkStyle.strokeWidth !== void 0) {
4699
+ linkElement.style.strokeWidth = String(linkStyle.strokeWidth);
4700
+ }
4701
+ if (linkStyle.opacity !== void 0) {
4702
+ linkElement.style.opacity = String(linkStyle.opacity);
4703
+ }
4704
+ if (linkStyle.stroke !== void 0 && renderableLink.style.arrow.enabled) {
4705
+ const selectionMarkerStyle = {
4706
+ stroke: linkStyle.stroke,
4707
+ arrow: { fill: linkStyle.stroke, size: renderableLink.style.arrow.size }
4708
+ };
4709
+ const selectionMarkerId = createArrowMarker({ svg: layers.svg, style: selectionMarkerStyle });
4710
+ select_default2(linkElement).attr("marker-end", `url(#${selectionMarkerId})`);
4711
+ }
4712
+ }
4713
+ linkSelectHandlers.forEach((handler) => handler(renderableLink.link, linkElement));
4714
+ };
4715
+ linkSelection.on("click.select", function(event, renderableLink) {
4716
+ selectLink(event, renderableLink, this);
4717
+ });
4718
+ 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);
4719
+ simulation.on("tick.hitarea", () => {
4720
+ 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);
4721
+ });
4722
+ linkHitAreaSelection.on("click.select", function(event, renderableLink) {
4723
+ const visibleLinkNode = linkSelection.filter((d) => d === renderableLink).node();
4724
+ if (visibleLinkNode) {
4725
+ selectLink(event, renderableLink, visibleLinkNode);
4726
+ }
4727
+ });
4728
+ select_default2(layers.svg).on("click.deselect", () => {
4729
+ deselectNode();
4730
+ deselectLink();
4731
+ });
4732
+ }
4635
4733
  if (config.controls?.enabled) {
4636
4734
  controls = createGraphControls(
4637
4735
  layers.overlay,
4638
- { zoomIn, zoomOut, resetView, fitView, destroy, render, exportGraph },
4736
+ { zoomIn, zoomOut, resetView, fitView },
4639
4737
  config.controls
4640
4738
  );
4641
4739
  controls.mount();
@@ -4645,25 +4743,35 @@ function createGraph(config) {
4645
4743
  }
4646
4744
  }
4647
4745
  function resetView() {
4648
- if (!zoomBehavior || !svgElement) return;
4649
- select_default2(svgElement).transition().call(zoomBehavior.transform, identity2);
4746
+ if (!zoomBehavior || !svgElement) {
4747
+ return;
4748
+ }
4749
+ select_default2(svgElement).transition().duration(400).call(zoomBehavior.transform, identity2);
4650
4750
  }
4651
4751
  function fitView() {
4652
- if (!zoomBehavior || !rootGroup || !svgElement || dimensions.width === 0 || dimensions.height === 0) return;
4752
+ if (!zoomBehavior || !rootGroup || !svgElement || dimensions.width === 0 || dimensions.height === 0) {
4753
+ return;
4754
+ }
4653
4755
  const bounds = rootGroup.getBBox();
4654
- if (bounds.width === 0 || bounds.height === 0) return;
4756
+ if (bounds.width === 0 || bounds.height === 0) {
4757
+ return;
4758
+ }
4655
4759
  const scale = Math.min(dimensions.width / bounds.width, dimensions.height / bounds.height) * 0.9;
4656
4760
  const translateX = (dimensions.width - bounds.width * scale) / 2 - bounds.x * scale;
4657
4761
  const translateY = (dimensions.height - bounds.height * scale) / 2 - bounds.y * scale;
4658
4762
  const transform2 = identity2.translate(translateX, translateY).scale(scale);
4659
- select_default2(svgElement).transition().call(zoomBehavior.transform, transform2);
4763
+ select_default2(svgElement).transition().duration(400).call(zoomBehavior.transform, transform2);
4660
4764
  }
4661
4765
  function zoomIn() {
4662
- if (!zoomBehavior || !svgElement) return;
4766
+ if (!zoomBehavior || !svgElement) {
4767
+ return;
4768
+ }
4663
4769
  select_default2(svgElement).transition().call(zoomBehavior.scaleBy, 1.2);
4664
4770
  }
4665
4771
  function zoomOut() {
4666
- if (!zoomBehavior || !svgElement) return;
4772
+ if (!zoomBehavior || !svgElement) {
4773
+ return;
4774
+ }
4667
4775
  select_default2(svgElement).transition().call(zoomBehavior.scaleBy, 0.8);
4668
4776
  }
4669
4777
  async function exportGraph(fileName) {
@@ -4675,6 +4783,10 @@ function createGraph(config) {
4675
4783
  });
4676
4784
  }
4677
4785
  function destroy() {
4786
+ if (fitViewTimer) {
4787
+ clearTimeout(fitViewTimer);
4788
+ fitViewTimer = null;
4789
+ }
4678
4790
  if (cleanupResize) {
4679
4791
  cleanupResize();
4680
4792
  cleanupResize = null;
@@ -4706,7 +4818,7 @@ function createGraph(config) {
4706
4818
  config.container.removeChild(config.container.firstChild);
4707
4819
  }
4708
4820
  }
4709
- return { render, zoomIn, zoomOut, resetView, fitView, destroy, exportGraph };
4821
+ return { render, zoomIn, zoomOut, resetView, fitView, destroy, exportGraph, on, off };
4710
4822
  }
4711
4823
  export {
4712
4824
  createGraph
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "polly-graph",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "description": "Reusable D3-based graph visualization SDK with configurable nodes, links, labels, interactions, and layout behaviors.",
5
5
  "license": "MIT",
6
6
  "author": "Badal",