polly-graph 0.1.5 → 0.1.7
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 +221 -9
- package/dist/index.d.cts +8 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +221 -9
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -4082,7 +4082,7 @@ function getLinkKey(link) {
|
|
|
4082
4082
|
}
|
|
4083
4083
|
function renderLinks(ctx, links) {
|
|
4084
4084
|
const renderableLinks = createRenderableLinks(ctx, links);
|
|
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");
|
|
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
4086
|
const labelSelection = ctx.root.selectAll(".link-label");
|
|
4087
4087
|
linkSelection.on("mouseenter.label-hover", (_event, d) => {
|
|
4088
4088
|
labelSelection.filter((labelItem) => labelItem.link === d.link && labelItem.style.label.visibility === "hover").interrupt().transition().duration(200).style("opacity", 1);
|
|
@@ -4236,7 +4236,9 @@ function createNodeHover(nodeSelection, hoverStyle) {
|
|
|
4236
4236
|
return s.id === d.id || t.id === d.id;
|
|
4237
4237
|
}).interrupt().transition().duration(200).style("opacity", 1).style("pointer-events", "auto");
|
|
4238
4238
|
}).on("mouseleave.labels", (_event) => {
|
|
4239
|
-
labelSelection.filter((item)
|
|
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");
|
|
4240
4242
|
});
|
|
4241
4243
|
}
|
|
4242
4244
|
|
|
@@ -4522,6 +4524,60 @@ async function captureAndDownloadGraph(container, options = {}) {
|
|
|
4522
4524
|
}
|
|
4523
4525
|
}
|
|
4524
4526
|
|
|
4527
|
+
// src/utils/node-link-selection.utils.ts
|
|
4528
|
+
function deselectNode(nodeElement, root2, nodeSelectHandlers, nodeDeselectHandlers) {
|
|
4529
|
+
nodeElement.style.fill = "";
|
|
4530
|
+
nodeElement.style.stroke = "";
|
|
4531
|
+
nodeElement.style.strokeWidth = "";
|
|
4532
|
+
nodeElement.style.opacity = "";
|
|
4533
|
+
nodeElement.style.removeProperty("r");
|
|
4534
|
+
root2.selectAll(".link-label.label-selection-pinned").classed("label-selection-pinned", false).interrupt().transition().duration(200).style("opacity", 0).style("pointer-events", "none");
|
|
4535
|
+
const nodeData = select_default2(nodeElement).datum();
|
|
4536
|
+
nodeSelectHandlers.clear();
|
|
4537
|
+
nodeDeselectHandlers.forEach((handler) => handler(nodeData, nodeElement));
|
|
4538
|
+
}
|
|
4539
|
+
function deselectLink(linkElement, linkMarkerSnapshots, linkSelectHandlers, linkDeselectHandlers) {
|
|
4540
|
+
linkElement.style.stroke = "";
|
|
4541
|
+
linkElement.style.strokeWidth = "";
|
|
4542
|
+
linkElement.style.opacity = "";
|
|
4543
|
+
const originalMarkerEnd = linkMarkerSnapshots.get(linkElement);
|
|
4544
|
+
if (originalMarkerEnd) {
|
|
4545
|
+
linkElement.setAttribute("marker-end", originalMarkerEnd);
|
|
4546
|
+
} else {
|
|
4547
|
+
linkElement.removeAttribute("marker-end");
|
|
4548
|
+
}
|
|
4549
|
+
const linkData = select_default2(linkElement).datum().link;
|
|
4550
|
+
linkSelectHandlers.clear();
|
|
4551
|
+
linkDeselectHandlers.forEach((handler) => handler(linkData, linkElement));
|
|
4552
|
+
}
|
|
4553
|
+
function selectLink(event, renderableLink, linkElement, selectionConfig, layers, linkSelectHandlers) {
|
|
4554
|
+
event.stopPropagation();
|
|
4555
|
+
const linkStyle = selectionConfig.linkStyle;
|
|
4556
|
+
if (linkStyle) {
|
|
4557
|
+
if (linkStyle.stroke !== void 0) {
|
|
4558
|
+
linkElement.style.stroke = linkStyle.stroke;
|
|
4559
|
+
}
|
|
4560
|
+
if (linkStyle.strokeWidth !== void 0) {
|
|
4561
|
+
linkElement.style.strokeWidth = String(linkStyle.strokeWidth);
|
|
4562
|
+
}
|
|
4563
|
+
if (linkStyle.opacity !== void 0) {
|
|
4564
|
+
linkElement.style.opacity = String(linkStyle.opacity);
|
|
4565
|
+
}
|
|
4566
|
+
if (linkStyle.stroke !== void 0 && renderableLink.style.arrow.enabled) {
|
|
4567
|
+
const selectionMarkerStyle = {
|
|
4568
|
+
stroke: linkStyle.stroke,
|
|
4569
|
+
arrow: { fill: linkStyle.stroke, size: renderableLink.style.arrow.size }
|
|
4570
|
+
};
|
|
4571
|
+
const selectionMarkerId = createArrowMarker({ svg: layers.svg, style: selectionMarkerStyle });
|
|
4572
|
+
select_default2(linkElement).attr("marker-end", `url(#${selectionMarkerId})`);
|
|
4573
|
+
}
|
|
4574
|
+
}
|
|
4575
|
+
linkSelectHandlers.forEach((handler) => handler(renderableLink.link, linkElement));
|
|
4576
|
+
}
|
|
4577
|
+
function createLinkHitArea(root2, linkSelection) {
|
|
4578
|
+
return 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);
|
|
4579
|
+
}
|
|
4580
|
+
|
|
4525
4581
|
// src/create-graph.ts
|
|
4526
4582
|
function createGraph(config) {
|
|
4527
4583
|
let cleanupResize = null;
|
|
@@ -4535,6 +4591,45 @@ function createGraph(config) {
|
|
|
4535
4591
|
let svgElement = null;
|
|
4536
4592
|
let zoomBehavior = null;
|
|
4537
4593
|
let simulation = null;
|
|
4594
|
+
const nodeSelectHandlers = /* @__PURE__ */ new Set();
|
|
4595
|
+
const nodeDeselectHandlers = /* @__PURE__ */ new Set();
|
|
4596
|
+
const linkSelectHandlers = /* @__PURE__ */ new Set();
|
|
4597
|
+
const linkDeselectHandlers = /* @__PURE__ */ new Set();
|
|
4598
|
+
function on(event, handler) {
|
|
4599
|
+
if (event === "nodeSelect") {
|
|
4600
|
+
nodeSelectHandlers.add(handler);
|
|
4601
|
+
return () => {
|
|
4602
|
+
nodeSelectHandlers.delete(handler);
|
|
4603
|
+
};
|
|
4604
|
+
}
|
|
4605
|
+
if (event === "nodeDeselect") {
|
|
4606
|
+
nodeDeselectHandlers.add(handler);
|
|
4607
|
+
return () => {
|
|
4608
|
+
nodeDeselectHandlers.delete(handler);
|
|
4609
|
+
};
|
|
4610
|
+
}
|
|
4611
|
+
if (event === "linkSelect") {
|
|
4612
|
+
linkSelectHandlers.add(handler);
|
|
4613
|
+
return () => {
|
|
4614
|
+
linkSelectHandlers.delete(handler);
|
|
4615
|
+
};
|
|
4616
|
+
}
|
|
4617
|
+
linkDeselectHandlers.add(handler);
|
|
4618
|
+
return () => {
|
|
4619
|
+
linkDeselectHandlers.delete(handler);
|
|
4620
|
+
};
|
|
4621
|
+
}
|
|
4622
|
+
function off(event, handler) {
|
|
4623
|
+
if (event === "nodeSelect") {
|
|
4624
|
+
nodeSelectHandlers.delete(handler);
|
|
4625
|
+
} else if (event === "nodeDeselect") {
|
|
4626
|
+
nodeDeselectHandlers.delete(handler);
|
|
4627
|
+
} else if (event === "linkSelect") {
|
|
4628
|
+
linkSelectHandlers.delete(handler);
|
|
4629
|
+
} else {
|
|
4630
|
+
linkDeselectHandlers.delete(handler);
|
|
4631
|
+
}
|
|
4632
|
+
}
|
|
4538
4633
|
function render() {
|
|
4539
4634
|
destroy();
|
|
4540
4635
|
const layers = createGraphLayers(config.container);
|
|
@@ -4618,10 +4713,117 @@ function createGraph(config) {
|
|
|
4618
4713
|
if (config.interaction?.drag?.enabled !== false) {
|
|
4619
4714
|
nodeSelection.call(createDragBehavior(simulation));
|
|
4620
4715
|
}
|
|
4716
|
+
const selectionConfig = config.interaction?.selection;
|
|
4717
|
+
if (selectionConfig?.enabled) {
|
|
4718
|
+
let selectedNodeElement = null;
|
|
4719
|
+
let selectedLinkElement = null;
|
|
4720
|
+
const linkMarkerSnapshots = /* @__PURE__ */ new Map();
|
|
4721
|
+
linkSelection.each(function() {
|
|
4722
|
+
const linkElement = this;
|
|
4723
|
+
linkMarkerSnapshots.set(linkElement, linkElement.getAttribute("marker-end"));
|
|
4724
|
+
});
|
|
4725
|
+
nodeSelection.on("click.select", function(event, node) {
|
|
4726
|
+
event.stopPropagation();
|
|
4727
|
+
const nodeElement = this;
|
|
4728
|
+
if (selectedNodeElement === nodeElement) {
|
|
4729
|
+
deselectNode(nodeElement, root2, nodeSelectHandlers, nodeDeselectHandlers);
|
|
4730
|
+
selectedNodeElement = null;
|
|
4731
|
+
return;
|
|
4732
|
+
}
|
|
4733
|
+
if (selectedNodeElement) {
|
|
4734
|
+
deselectNode(selectedNodeElement, root2, nodeSelectHandlers, nodeDeselectHandlers);
|
|
4735
|
+
selectedNodeElement = null;
|
|
4736
|
+
}
|
|
4737
|
+
if (selectedLinkElement) {
|
|
4738
|
+
deselectLink(selectedLinkElement, linkMarkerSnapshots, linkSelectHandlers, linkDeselectHandlers);
|
|
4739
|
+
selectedLinkElement = null;
|
|
4740
|
+
}
|
|
4741
|
+
selectedNodeElement = nodeElement;
|
|
4742
|
+
const nodeStyle = selectionConfig.nodeStyle;
|
|
4743
|
+
if (nodeStyle) {
|
|
4744
|
+
if (nodeStyle.fill !== void 0) {
|
|
4745
|
+
nodeElement.style.fill = nodeStyle.fill;
|
|
4746
|
+
}
|
|
4747
|
+
if (nodeStyle.stroke !== void 0) {
|
|
4748
|
+
nodeElement.style.stroke = nodeStyle.stroke;
|
|
4749
|
+
}
|
|
4750
|
+
if (nodeStyle.strokeWidth !== void 0) {
|
|
4751
|
+
nodeElement.style.strokeWidth = String(nodeStyle.strokeWidth);
|
|
4752
|
+
}
|
|
4753
|
+
if (nodeStyle.opacity !== void 0) {
|
|
4754
|
+
nodeElement.style.opacity = String(nodeStyle.opacity);
|
|
4755
|
+
}
|
|
4756
|
+
if (nodeStyle.radius !== void 0) {
|
|
4757
|
+
nodeElement.style.setProperty("r", String(nodeStyle.radius));
|
|
4758
|
+
}
|
|
4759
|
+
}
|
|
4760
|
+
root2.selectAll(".link-label").filter((item) => {
|
|
4761
|
+
if (item.style.label.visibility !== "hover") {
|
|
4762
|
+
return false;
|
|
4763
|
+
}
|
|
4764
|
+
const source = item.link.source;
|
|
4765
|
+
const target = item.link.target;
|
|
4766
|
+
return source.id === node.id || target.id === node.id;
|
|
4767
|
+
}).classed("label-selection-pinned", true).interrupt().transition().duration(200).style("opacity", 1).style("pointer-events", "auto");
|
|
4768
|
+
nodeSelectHandlers.forEach((handler) => handler(node, nodeElement));
|
|
4769
|
+
});
|
|
4770
|
+
linkSelection.on("click.select", function(event, renderableLink) {
|
|
4771
|
+
const linkElement = this;
|
|
4772
|
+
if (selectedLinkElement === linkElement) {
|
|
4773
|
+
deselectLink(linkElement, linkMarkerSnapshots, linkSelectHandlers, linkDeselectHandlers);
|
|
4774
|
+
selectedLinkElement = null;
|
|
4775
|
+
return;
|
|
4776
|
+
}
|
|
4777
|
+
if (selectedLinkElement) {
|
|
4778
|
+
deselectLink(selectedLinkElement, linkMarkerSnapshots, linkSelectHandlers, linkDeselectHandlers);
|
|
4779
|
+
selectedLinkElement = null;
|
|
4780
|
+
}
|
|
4781
|
+
if (selectedNodeElement) {
|
|
4782
|
+
deselectNode(selectedNodeElement, root2, nodeSelectHandlers, nodeDeselectHandlers);
|
|
4783
|
+
selectedNodeElement = null;
|
|
4784
|
+
}
|
|
4785
|
+
selectedLinkElement = linkElement;
|
|
4786
|
+
selectLink(event, renderableLink, linkElement, selectionConfig, layers, linkSelectHandlers);
|
|
4787
|
+
});
|
|
4788
|
+
const linkHitAreaSelection = createLinkHitArea(root2, linkSelection);
|
|
4789
|
+
simulation.on("tick.hitarea", () => {
|
|
4790
|
+
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);
|
|
4791
|
+
});
|
|
4792
|
+
linkHitAreaSelection.on("click.select", function(event, renderableLink) {
|
|
4793
|
+
const visibleLinkNode = linkSelection.filter((d) => d === renderableLink).node();
|
|
4794
|
+
if (visibleLinkNode) {
|
|
4795
|
+
if (selectedLinkElement === visibleLinkNode) {
|
|
4796
|
+
deselectLink(visibleLinkNode, linkMarkerSnapshots, linkSelectHandlers, linkDeselectHandlers);
|
|
4797
|
+
selectedLinkElement = null;
|
|
4798
|
+
return;
|
|
4799
|
+
}
|
|
4800
|
+
if (selectedLinkElement) {
|
|
4801
|
+
deselectLink(selectedLinkElement, linkMarkerSnapshots, linkSelectHandlers, linkDeselectHandlers);
|
|
4802
|
+
selectedLinkElement = null;
|
|
4803
|
+
}
|
|
4804
|
+
if (selectedNodeElement) {
|
|
4805
|
+
deselectNode(selectedNodeElement, root2, nodeSelectHandlers, nodeDeselectHandlers);
|
|
4806
|
+
selectedNodeElement = null;
|
|
4807
|
+
}
|
|
4808
|
+
selectedLinkElement = visibleLinkNode;
|
|
4809
|
+
selectLink(event, renderableLink, visibleLinkNode, selectionConfig, layers, linkSelectHandlers);
|
|
4810
|
+
}
|
|
4811
|
+
});
|
|
4812
|
+
select_default2(layers.svg).on("click.deselect", () => {
|
|
4813
|
+
if (selectedNodeElement) {
|
|
4814
|
+
deselectNode(selectedNodeElement, root2, nodeSelectHandlers, nodeDeselectHandlers);
|
|
4815
|
+
selectedNodeElement = null;
|
|
4816
|
+
}
|
|
4817
|
+
if (selectedLinkElement) {
|
|
4818
|
+
deselectLink(selectedLinkElement, linkMarkerSnapshots, linkSelectHandlers, linkDeselectHandlers);
|
|
4819
|
+
selectedLinkElement = null;
|
|
4820
|
+
}
|
|
4821
|
+
});
|
|
4822
|
+
}
|
|
4621
4823
|
if (config.controls?.enabled) {
|
|
4622
4824
|
controls = createGraphControls(
|
|
4623
4825
|
layers.overlay,
|
|
4624
|
-
{ zoomIn, zoomOut, resetView, fitView
|
|
4826
|
+
{ zoomIn, zoomOut, resetView, fitView },
|
|
4625
4827
|
config.controls
|
|
4626
4828
|
);
|
|
4627
4829
|
controls.mount();
|
|
@@ -4631,13 +4833,19 @@ function createGraph(config) {
|
|
|
4631
4833
|
}
|
|
4632
4834
|
}
|
|
4633
4835
|
function resetView() {
|
|
4634
|
-
if (!zoomBehavior || !svgElement)
|
|
4836
|
+
if (!zoomBehavior || !svgElement) {
|
|
4837
|
+
return;
|
|
4838
|
+
}
|
|
4635
4839
|
select_default2(svgElement).transition().duration(400).call(zoomBehavior.transform, identity2);
|
|
4636
4840
|
}
|
|
4637
4841
|
function fitView() {
|
|
4638
|
-
if (!zoomBehavior || !rootGroup || !svgElement || dimensions.width === 0 || dimensions.height === 0)
|
|
4842
|
+
if (!zoomBehavior || !rootGroup || !svgElement || dimensions.width === 0 || dimensions.height === 0) {
|
|
4843
|
+
return;
|
|
4844
|
+
}
|
|
4639
4845
|
const bounds = rootGroup.getBBox();
|
|
4640
|
-
if (bounds.width === 0 || bounds.height === 0)
|
|
4846
|
+
if (bounds.width === 0 || bounds.height === 0) {
|
|
4847
|
+
return;
|
|
4848
|
+
}
|
|
4641
4849
|
const scale = Math.min(dimensions.width / bounds.width, dimensions.height / bounds.height) * 0.9;
|
|
4642
4850
|
const translateX = (dimensions.width - bounds.width * scale) / 2 - bounds.x * scale;
|
|
4643
4851
|
const translateY = (dimensions.height - bounds.height * scale) / 2 - bounds.y * scale;
|
|
@@ -4645,11 +4853,15 @@ function createGraph(config) {
|
|
|
4645
4853
|
select_default2(svgElement).transition().duration(400).call(zoomBehavior.transform, transform2);
|
|
4646
4854
|
}
|
|
4647
4855
|
function zoomIn() {
|
|
4648
|
-
if (!zoomBehavior || !svgElement)
|
|
4856
|
+
if (!zoomBehavior || !svgElement) {
|
|
4857
|
+
return;
|
|
4858
|
+
}
|
|
4649
4859
|
select_default2(svgElement).transition().call(zoomBehavior.scaleBy, 1.2);
|
|
4650
4860
|
}
|
|
4651
4861
|
function zoomOut() {
|
|
4652
|
-
if (!zoomBehavior || !svgElement)
|
|
4862
|
+
if (!zoomBehavior || !svgElement) {
|
|
4863
|
+
return;
|
|
4864
|
+
}
|
|
4653
4865
|
select_default2(svgElement).transition().call(zoomBehavior.scaleBy, 0.8);
|
|
4654
4866
|
}
|
|
4655
4867
|
async function exportGraph(fileName) {
|
|
@@ -4696,7 +4908,7 @@ function createGraph(config) {
|
|
|
4696
4908
|
config.container.removeChild(config.container.firstChild);
|
|
4697
4909
|
}
|
|
4698
4910
|
}
|
|
4699
|
-
return { render, zoomIn, zoomOut, resetView, fitView, destroy, exportGraph };
|
|
4911
|
+
return { render, zoomIn, zoomOut, resetView, fitView, destroy, exportGraph, on, off };
|
|
4700
4912
|
}
|
|
4701
4913
|
// Annotate the CommonJS export names for ESM import in node:
|
|
4702
4914
|
0 && (module.exports = {
|
package/dist/index.d.cts
CHANGED
|
@@ -138,6 +138,14 @@ interface GraphInstance {
|
|
|
138
138
|
fitView(): void;
|
|
139
139
|
destroy(): void;
|
|
140
140
|
exportGraph(fileName?: string): void;
|
|
141
|
+
on(event: 'nodeSelect', handler: (node: GraphNode, element: SVGCircleElement) => void): () => void;
|
|
142
|
+
on(event: 'nodeDeselect', handler: (node: GraphNode, element: SVGCircleElement) => void): () => void;
|
|
143
|
+
on(event: 'linkSelect', handler: (link: GraphLink, element: SVGLineElement) => void): () => void;
|
|
144
|
+
on(event: 'linkDeselect', handler: (link: GraphLink, element: SVGLineElement) => void): () => void;
|
|
145
|
+
off(event: 'nodeSelect', handler: (node: GraphNode, element: SVGCircleElement) => void): void;
|
|
146
|
+
off(event: 'nodeDeselect', handler: (node: GraphNode, element: SVGCircleElement) => void): void;
|
|
147
|
+
off(event: 'linkSelect', handler: (link: GraphLink, element: SVGLineElement) => void): void;
|
|
148
|
+
off(event: 'linkDeselect', handler: (link: GraphLink, element: SVGLineElement) => void): void;
|
|
141
149
|
}
|
|
142
150
|
|
|
143
151
|
declare function createGraph(config: GraphConfig): GraphInstance;
|
package/dist/index.d.ts
CHANGED
|
@@ -138,6 +138,14 @@ interface GraphInstance {
|
|
|
138
138
|
fitView(): void;
|
|
139
139
|
destroy(): void;
|
|
140
140
|
exportGraph(fileName?: string): void;
|
|
141
|
+
on(event: 'nodeSelect', handler: (node: GraphNode, element: SVGCircleElement) => void): () => void;
|
|
142
|
+
on(event: 'nodeDeselect', handler: (node: GraphNode, element: SVGCircleElement) => void): () => void;
|
|
143
|
+
on(event: 'linkSelect', handler: (link: GraphLink, element: SVGLineElement) => void): () => void;
|
|
144
|
+
on(event: 'linkDeselect', handler: (link: GraphLink, element: SVGLineElement) => void): () => void;
|
|
145
|
+
off(event: 'nodeSelect', handler: (node: GraphNode, element: SVGCircleElement) => void): void;
|
|
146
|
+
off(event: 'nodeDeselect', handler: (node: GraphNode, element: SVGCircleElement) => void): void;
|
|
147
|
+
off(event: 'linkSelect', handler: (link: GraphLink, element: SVGLineElement) => void): void;
|
|
148
|
+
off(event: 'linkDeselect', handler: (link: GraphLink, element: SVGLineElement) => void): void;
|
|
141
149
|
}
|
|
142
150
|
|
|
143
151
|
declare function createGraph(config: GraphConfig): GraphInstance;
|
package/dist/index.js
CHANGED
|
@@ -4046,7 +4046,7 @@ function getLinkKey(link) {
|
|
|
4046
4046
|
}
|
|
4047
4047
|
function renderLinks(ctx, links) {
|
|
4048
4048
|
const renderableLinks = createRenderableLinks(ctx, links);
|
|
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");
|
|
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
4050
|
const labelSelection = ctx.root.selectAll(".link-label");
|
|
4051
4051
|
linkSelection.on("mouseenter.label-hover", (_event, d) => {
|
|
4052
4052
|
labelSelection.filter((labelItem) => labelItem.link === d.link && labelItem.style.label.visibility === "hover").interrupt().transition().duration(200).style("opacity", 1);
|
|
@@ -4200,7 +4200,9 @@ function createNodeHover(nodeSelection, hoverStyle) {
|
|
|
4200
4200
|
return s.id === d.id || t.id === d.id;
|
|
4201
4201
|
}).interrupt().transition().duration(200).style("opacity", 1).style("pointer-events", "auto");
|
|
4202
4202
|
}).on("mouseleave.labels", (_event) => {
|
|
4203
|
-
labelSelection.filter((item)
|
|
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");
|
|
4204
4206
|
});
|
|
4205
4207
|
}
|
|
4206
4208
|
|
|
@@ -4486,6 +4488,60 @@ async function captureAndDownloadGraph(container, options = {}) {
|
|
|
4486
4488
|
}
|
|
4487
4489
|
}
|
|
4488
4490
|
|
|
4491
|
+
// src/utils/node-link-selection.utils.ts
|
|
4492
|
+
function deselectNode(nodeElement, root2, nodeSelectHandlers, nodeDeselectHandlers) {
|
|
4493
|
+
nodeElement.style.fill = "";
|
|
4494
|
+
nodeElement.style.stroke = "";
|
|
4495
|
+
nodeElement.style.strokeWidth = "";
|
|
4496
|
+
nodeElement.style.opacity = "";
|
|
4497
|
+
nodeElement.style.removeProperty("r");
|
|
4498
|
+
root2.selectAll(".link-label.label-selection-pinned").classed("label-selection-pinned", false).interrupt().transition().duration(200).style("opacity", 0).style("pointer-events", "none");
|
|
4499
|
+
const nodeData = select_default2(nodeElement).datum();
|
|
4500
|
+
nodeSelectHandlers.clear();
|
|
4501
|
+
nodeDeselectHandlers.forEach((handler) => handler(nodeData, nodeElement));
|
|
4502
|
+
}
|
|
4503
|
+
function deselectLink(linkElement, linkMarkerSnapshots, linkSelectHandlers, linkDeselectHandlers) {
|
|
4504
|
+
linkElement.style.stroke = "";
|
|
4505
|
+
linkElement.style.strokeWidth = "";
|
|
4506
|
+
linkElement.style.opacity = "";
|
|
4507
|
+
const originalMarkerEnd = linkMarkerSnapshots.get(linkElement);
|
|
4508
|
+
if (originalMarkerEnd) {
|
|
4509
|
+
linkElement.setAttribute("marker-end", originalMarkerEnd);
|
|
4510
|
+
} else {
|
|
4511
|
+
linkElement.removeAttribute("marker-end");
|
|
4512
|
+
}
|
|
4513
|
+
const linkData = select_default2(linkElement).datum().link;
|
|
4514
|
+
linkSelectHandlers.clear();
|
|
4515
|
+
linkDeselectHandlers.forEach((handler) => handler(linkData, linkElement));
|
|
4516
|
+
}
|
|
4517
|
+
function selectLink(event, renderableLink, linkElement, selectionConfig, layers, linkSelectHandlers) {
|
|
4518
|
+
event.stopPropagation();
|
|
4519
|
+
const linkStyle = selectionConfig.linkStyle;
|
|
4520
|
+
if (linkStyle) {
|
|
4521
|
+
if (linkStyle.stroke !== void 0) {
|
|
4522
|
+
linkElement.style.stroke = linkStyle.stroke;
|
|
4523
|
+
}
|
|
4524
|
+
if (linkStyle.strokeWidth !== void 0) {
|
|
4525
|
+
linkElement.style.strokeWidth = String(linkStyle.strokeWidth);
|
|
4526
|
+
}
|
|
4527
|
+
if (linkStyle.opacity !== void 0) {
|
|
4528
|
+
linkElement.style.opacity = String(linkStyle.opacity);
|
|
4529
|
+
}
|
|
4530
|
+
if (linkStyle.stroke !== void 0 && renderableLink.style.arrow.enabled) {
|
|
4531
|
+
const selectionMarkerStyle = {
|
|
4532
|
+
stroke: linkStyle.stroke,
|
|
4533
|
+
arrow: { fill: linkStyle.stroke, size: renderableLink.style.arrow.size }
|
|
4534
|
+
};
|
|
4535
|
+
const selectionMarkerId = createArrowMarker({ svg: layers.svg, style: selectionMarkerStyle });
|
|
4536
|
+
select_default2(linkElement).attr("marker-end", `url(#${selectionMarkerId})`);
|
|
4537
|
+
}
|
|
4538
|
+
}
|
|
4539
|
+
linkSelectHandlers.forEach((handler) => handler(renderableLink.link, linkElement));
|
|
4540
|
+
}
|
|
4541
|
+
function createLinkHitArea(root2, linkSelection) {
|
|
4542
|
+
return 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);
|
|
4543
|
+
}
|
|
4544
|
+
|
|
4489
4545
|
// src/create-graph.ts
|
|
4490
4546
|
function createGraph(config) {
|
|
4491
4547
|
let cleanupResize = null;
|
|
@@ -4499,6 +4555,45 @@ function createGraph(config) {
|
|
|
4499
4555
|
let svgElement = null;
|
|
4500
4556
|
let zoomBehavior = null;
|
|
4501
4557
|
let simulation = null;
|
|
4558
|
+
const nodeSelectHandlers = /* @__PURE__ */ new Set();
|
|
4559
|
+
const nodeDeselectHandlers = /* @__PURE__ */ new Set();
|
|
4560
|
+
const linkSelectHandlers = /* @__PURE__ */ new Set();
|
|
4561
|
+
const linkDeselectHandlers = /* @__PURE__ */ new Set();
|
|
4562
|
+
function on(event, handler) {
|
|
4563
|
+
if (event === "nodeSelect") {
|
|
4564
|
+
nodeSelectHandlers.add(handler);
|
|
4565
|
+
return () => {
|
|
4566
|
+
nodeSelectHandlers.delete(handler);
|
|
4567
|
+
};
|
|
4568
|
+
}
|
|
4569
|
+
if (event === "nodeDeselect") {
|
|
4570
|
+
nodeDeselectHandlers.add(handler);
|
|
4571
|
+
return () => {
|
|
4572
|
+
nodeDeselectHandlers.delete(handler);
|
|
4573
|
+
};
|
|
4574
|
+
}
|
|
4575
|
+
if (event === "linkSelect") {
|
|
4576
|
+
linkSelectHandlers.add(handler);
|
|
4577
|
+
return () => {
|
|
4578
|
+
linkSelectHandlers.delete(handler);
|
|
4579
|
+
};
|
|
4580
|
+
}
|
|
4581
|
+
linkDeselectHandlers.add(handler);
|
|
4582
|
+
return () => {
|
|
4583
|
+
linkDeselectHandlers.delete(handler);
|
|
4584
|
+
};
|
|
4585
|
+
}
|
|
4586
|
+
function off(event, handler) {
|
|
4587
|
+
if (event === "nodeSelect") {
|
|
4588
|
+
nodeSelectHandlers.delete(handler);
|
|
4589
|
+
} else if (event === "nodeDeselect") {
|
|
4590
|
+
nodeDeselectHandlers.delete(handler);
|
|
4591
|
+
} else if (event === "linkSelect") {
|
|
4592
|
+
linkSelectHandlers.delete(handler);
|
|
4593
|
+
} else {
|
|
4594
|
+
linkDeselectHandlers.delete(handler);
|
|
4595
|
+
}
|
|
4596
|
+
}
|
|
4502
4597
|
function render() {
|
|
4503
4598
|
destroy();
|
|
4504
4599
|
const layers = createGraphLayers(config.container);
|
|
@@ -4582,10 +4677,117 @@ function createGraph(config) {
|
|
|
4582
4677
|
if (config.interaction?.drag?.enabled !== false) {
|
|
4583
4678
|
nodeSelection.call(createDragBehavior(simulation));
|
|
4584
4679
|
}
|
|
4680
|
+
const selectionConfig = config.interaction?.selection;
|
|
4681
|
+
if (selectionConfig?.enabled) {
|
|
4682
|
+
let selectedNodeElement = null;
|
|
4683
|
+
let selectedLinkElement = null;
|
|
4684
|
+
const linkMarkerSnapshots = /* @__PURE__ */ new Map();
|
|
4685
|
+
linkSelection.each(function() {
|
|
4686
|
+
const linkElement = this;
|
|
4687
|
+
linkMarkerSnapshots.set(linkElement, linkElement.getAttribute("marker-end"));
|
|
4688
|
+
});
|
|
4689
|
+
nodeSelection.on("click.select", function(event, node) {
|
|
4690
|
+
event.stopPropagation();
|
|
4691
|
+
const nodeElement = this;
|
|
4692
|
+
if (selectedNodeElement === nodeElement) {
|
|
4693
|
+
deselectNode(nodeElement, root2, nodeSelectHandlers, nodeDeselectHandlers);
|
|
4694
|
+
selectedNodeElement = null;
|
|
4695
|
+
return;
|
|
4696
|
+
}
|
|
4697
|
+
if (selectedNodeElement) {
|
|
4698
|
+
deselectNode(selectedNodeElement, root2, nodeSelectHandlers, nodeDeselectHandlers);
|
|
4699
|
+
selectedNodeElement = null;
|
|
4700
|
+
}
|
|
4701
|
+
if (selectedLinkElement) {
|
|
4702
|
+
deselectLink(selectedLinkElement, linkMarkerSnapshots, linkSelectHandlers, linkDeselectHandlers);
|
|
4703
|
+
selectedLinkElement = null;
|
|
4704
|
+
}
|
|
4705
|
+
selectedNodeElement = nodeElement;
|
|
4706
|
+
const nodeStyle = selectionConfig.nodeStyle;
|
|
4707
|
+
if (nodeStyle) {
|
|
4708
|
+
if (nodeStyle.fill !== void 0) {
|
|
4709
|
+
nodeElement.style.fill = nodeStyle.fill;
|
|
4710
|
+
}
|
|
4711
|
+
if (nodeStyle.stroke !== void 0) {
|
|
4712
|
+
nodeElement.style.stroke = nodeStyle.stroke;
|
|
4713
|
+
}
|
|
4714
|
+
if (nodeStyle.strokeWidth !== void 0) {
|
|
4715
|
+
nodeElement.style.strokeWidth = String(nodeStyle.strokeWidth);
|
|
4716
|
+
}
|
|
4717
|
+
if (nodeStyle.opacity !== void 0) {
|
|
4718
|
+
nodeElement.style.opacity = String(nodeStyle.opacity);
|
|
4719
|
+
}
|
|
4720
|
+
if (nodeStyle.radius !== void 0) {
|
|
4721
|
+
nodeElement.style.setProperty("r", String(nodeStyle.radius));
|
|
4722
|
+
}
|
|
4723
|
+
}
|
|
4724
|
+
root2.selectAll(".link-label").filter((item) => {
|
|
4725
|
+
if (item.style.label.visibility !== "hover") {
|
|
4726
|
+
return false;
|
|
4727
|
+
}
|
|
4728
|
+
const source = item.link.source;
|
|
4729
|
+
const target = item.link.target;
|
|
4730
|
+
return source.id === node.id || target.id === node.id;
|
|
4731
|
+
}).classed("label-selection-pinned", true).interrupt().transition().duration(200).style("opacity", 1).style("pointer-events", "auto");
|
|
4732
|
+
nodeSelectHandlers.forEach((handler) => handler(node, nodeElement));
|
|
4733
|
+
});
|
|
4734
|
+
linkSelection.on("click.select", function(event, renderableLink) {
|
|
4735
|
+
const linkElement = this;
|
|
4736
|
+
if (selectedLinkElement === linkElement) {
|
|
4737
|
+
deselectLink(linkElement, linkMarkerSnapshots, linkSelectHandlers, linkDeselectHandlers);
|
|
4738
|
+
selectedLinkElement = null;
|
|
4739
|
+
return;
|
|
4740
|
+
}
|
|
4741
|
+
if (selectedLinkElement) {
|
|
4742
|
+
deselectLink(selectedLinkElement, linkMarkerSnapshots, linkSelectHandlers, linkDeselectHandlers);
|
|
4743
|
+
selectedLinkElement = null;
|
|
4744
|
+
}
|
|
4745
|
+
if (selectedNodeElement) {
|
|
4746
|
+
deselectNode(selectedNodeElement, root2, nodeSelectHandlers, nodeDeselectHandlers);
|
|
4747
|
+
selectedNodeElement = null;
|
|
4748
|
+
}
|
|
4749
|
+
selectedLinkElement = linkElement;
|
|
4750
|
+
selectLink(event, renderableLink, linkElement, selectionConfig, layers, linkSelectHandlers);
|
|
4751
|
+
});
|
|
4752
|
+
const linkHitAreaSelection = createLinkHitArea(root2, linkSelection);
|
|
4753
|
+
simulation.on("tick.hitarea", () => {
|
|
4754
|
+
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);
|
|
4755
|
+
});
|
|
4756
|
+
linkHitAreaSelection.on("click.select", function(event, renderableLink) {
|
|
4757
|
+
const visibleLinkNode = linkSelection.filter((d) => d === renderableLink).node();
|
|
4758
|
+
if (visibleLinkNode) {
|
|
4759
|
+
if (selectedLinkElement === visibleLinkNode) {
|
|
4760
|
+
deselectLink(visibleLinkNode, linkMarkerSnapshots, linkSelectHandlers, linkDeselectHandlers);
|
|
4761
|
+
selectedLinkElement = null;
|
|
4762
|
+
return;
|
|
4763
|
+
}
|
|
4764
|
+
if (selectedLinkElement) {
|
|
4765
|
+
deselectLink(selectedLinkElement, linkMarkerSnapshots, linkSelectHandlers, linkDeselectHandlers);
|
|
4766
|
+
selectedLinkElement = null;
|
|
4767
|
+
}
|
|
4768
|
+
if (selectedNodeElement) {
|
|
4769
|
+
deselectNode(selectedNodeElement, root2, nodeSelectHandlers, nodeDeselectHandlers);
|
|
4770
|
+
selectedNodeElement = null;
|
|
4771
|
+
}
|
|
4772
|
+
selectedLinkElement = visibleLinkNode;
|
|
4773
|
+
selectLink(event, renderableLink, visibleLinkNode, selectionConfig, layers, linkSelectHandlers);
|
|
4774
|
+
}
|
|
4775
|
+
});
|
|
4776
|
+
select_default2(layers.svg).on("click.deselect", () => {
|
|
4777
|
+
if (selectedNodeElement) {
|
|
4778
|
+
deselectNode(selectedNodeElement, root2, nodeSelectHandlers, nodeDeselectHandlers);
|
|
4779
|
+
selectedNodeElement = null;
|
|
4780
|
+
}
|
|
4781
|
+
if (selectedLinkElement) {
|
|
4782
|
+
deselectLink(selectedLinkElement, linkMarkerSnapshots, linkSelectHandlers, linkDeselectHandlers);
|
|
4783
|
+
selectedLinkElement = null;
|
|
4784
|
+
}
|
|
4785
|
+
});
|
|
4786
|
+
}
|
|
4585
4787
|
if (config.controls?.enabled) {
|
|
4586
4788
|
controls = createGraphControls(
|
|
4587
4789
|
layers.overlay,
|
|
4588
|
-
{ zoomIn, zoomOut, resetView, fitView
|
|
4790
|
+
{ zoomIn, zoomOut, resetView, fitView },
|
|
4589
4791
|
config.controls
|
|
4590
4792
|
);
|
|
4591
4793
|
controls.mount();
|
|
@@ -4595,13 +4797,19 @@ function createGraph(config) {
|
|
|
4595
4797
|
}
|
|
4596
4798
|
}
|
|
4597
4799
|
function resetView() {
|
|
4598
|
-
if (!zoomBehavior || !svgElement)
|
|
4800
|
+
if (!zoomBehavior || !svgElement) {
|
|
4801
|
+
return;
|
|
4802
|
+
}
|
|
4599
4803
|
select_default2(svgElement).transition().duration(400).call(zoomBehavior.transform, identity2);
|
|
4600
4804
|
}
|
|
4601
4805
|
function fitView() {
|
|
4602
|
-
if (!zoomBehavior || !rootGroup || !svgElement || dimensions.width === 0 || dimensions.height === 0)
|
|
4806
|
+
if (!zoomBehavior || !rootGroup || !svgElement || dimensions.width === 0 || dimensions.height === 0) {
|
|
4807
|
+
return;
|
|
4808
|
+
}
|
|
4603
4809
|
const bounds = rootGroup.getBBox();
|
|
4604
|
-
if (bounds.width === 0 || bounds.height === 0)
|
|
4810
|
+
if (bounds.width === 0 || bounds.height === 0) {
|
|
4811
|
+
return;
|
|
4812
|
+
}
|
|
4605
4813
|
const scale = Math.min(dimensions.width / bounds.width, dimensions.height / bounds.height) * 0.9;
|
|
4606
4814
|
const translateX = (dimensions.width - bounds.width * scale) / 2 - bounds.x * scale;
|
|
4607
4815
|
const translateY = (dimensions.height - bounds.height * scale) / 2 - bounds.y * scale;
|
|
@@ -4609,11 +4817,15 @@ function createGraph(config) {
|
|
|
4609
4817
|
select_default2(svgElement).transition().duration(400).call(zoomBehavior.transform, transform2);
|
|
4610
4818
|
}
|
|
4611
4819
|
function zoomIn() {
|
|
4612
|
-
if (!zoomBehavior || !svgElement)
|
|
4820
|
+
if (!zoomBehavior || !svgElement) {
|
|
4821
|
+
return;
|
|
4822
|
+
}
|
|
4613
4823
|
select_default2(svgElement).transition().call(zoomBehavior.scaleBy, 1.2);
|
|
4614
4824
|
}
|
|
4615
4825
|
function zoomOut() {
|
|
4616
|
-
if (!zoomBehavior || !svgElement)
|
|
4826
|
+
if (!zoomBehavior || !svgElement) {
|
|
4827
|
+
return;
|
|
4828
|
+
}
|
|
4617
4829
|
select_default2(svgElement).transition().call(zoomBehavior.scaleBy, 0.8);
|
|
4618
4830
|
}
|
|
4619
4831
|
async function exportGraph(fileName) {
|
|
@@ -4660,7 +4872,7 @@ function createGraph(config) {
|
|
|
4660
4872
|
config.container.removeChild(config.container.firstChild);
|
|
4661
4873
|
}
|
|
4662
4874
|
}
|
|
4663
|
-
return { render, zoomIn, zoomOut, resetView, fitView, destroy, exportGraph };
|
|
4875
|
+
return { render, zoomIn, zoomOut, resetView, fitView, destroy, exportGraph, on, off };
|
|
4664
4876
|
}
|
|
4665
4877
|
export {
|
|
4666
4878
|
createGraph
|
package/package.json
CHANGED