我创建了一个工作正常的 d3 网络图,问题是(A -> B 和 B -> A)之间是否存在连接,我想要单独的链接。我怎样才能做到这一点?
这就是我到目前为止所做的
onMount(() => {
const svg = d3
.select(svgContainer)
.attr('width', width)
.attr('height', height);
const tooltip = d3.select(tooltipElement);
// Modify nodes to ensure they have vx, vy, x, y properties
const nodesWithPositions = data.nodes.map((node: CustomNode) => ({
...node,
vx: 0, // Initialize vx for D3 simulation
vy: 0, // Initialize vy for D3 simulation
x: node.x || 0, // Ensure x is initialized if missing
y: node.y || 0, // Ensure y is initialized if missing
}));
// Create a force simulation using nodes and links
const simulation = d3
.forceSimulation(nodesWithPositions) // Use the updated nodes
.force(
'link',
d3
.forceLink(data.links)
.id((d) => (d as CustomNode).id) // Use the correct id function
.distance(200) // Define the link distance
)
.force('charge', d3.forceManyBody().strength(-300)) // Charge force to push nodes apart
.force('center', d3.forceCenter(width / 2, height / 2)); // Center force
// Add links to the SVG and apply the arrow marker
const link = svg
.append('g')
.selectAll('line')
.data(data.links) // Use the original links (no duplication)
.enter()
.append('line')
.attr('stroke', '#999')
.attr('stroke-opacity', 0.6)
.attr('stroke-width', (d) => Math.sqrt(Number(d.cycleCount))) // Set link stroke width based on cycle count
// Add cycleCount label to the middle of each link
const linkText = svg
.append('g')
.selectAll('text')
.data(data.links)
.enter()
.append('text')
.attr('x', (d: any) => (d.source.x + d.target.x) / 2) // Calculate midpoint of the link
.attr('y', (d: any) => (d.source.y + d.target.y) / 2) // Calculate midpoint of the link
.attr('dy', -10) // Move the text slightly above the midpoint
.attr('text-anchor', 'middle')
.attr('font-size', 12)
.attr('fill', '#333')
.text((d: any) => d.cycleCount); // Display the cycle count
// Add nodes to the SVG
const node = svg
.append('g')
.selectAll('circle')
.data(nodesWithPositions) // Pass the updated nodes with vx, vy, x, y
.enter()
.append('circle')
.attr('r', 10) // Set the radius of the nodes
.attr('fill', '#69b3a2') // Set the fill color of the nodes
.attr('stroke', '#333') // Set the border color of the nodes
.attr('stroke-width', 1.5) // Set the border width
.call(
d3.drag<SVGCircleElement, CustomNode>()
.on('start', (event: d3.D3DragEvent<SVGCircleElement, CustomNode, CustomNode>, d: CustomNode) => {
if (!event.active) simulation.alphaTarget(0.3).restart();
d.fx = d.x; // Set the fixed x position
d.fy = d.y; // Set the fixed y position
})
.on('drag', (event: d3.D3DragEvent<SVGCircleElement, CustomNode, CustomNode>, d: CustomNode) => {
d.fx = event.x; // Update fixed x position during drag
d.fy = event.y; // Update fixed y position during drag
})
.on('end', (event: d3.D3DragEvent<SVGCircleElement, CustomNode, CustomNode>, d: CustomNode) => {
if (!event.active) simulation.alphaTarget(0);
d.fx = null; // Release the fixed x position
d.fy = null; // Release the fixed y position
})
);
// Add tooltips on node hover
node
.on('mouseover', (event, d: CustomNode) => {
tooltip
.classed('opacity-100', true)
.classed('opacity-0', false)
.style('left', `${event.pageX + 10}px`)
.style('top', `${event.pageY - 25}px`)
.html(`Geozone: ${d.name}`);
})
.on('mouseout', () => {
tooltip.classed('opacity-0', true).classed('opacity-100', false);
});
// Update link and node positions on each tick
simulation.on('tick', () => {
link
.attr('x1', (d: any) => d.source.x)
.attr('y1', (d: any) => d.source.y)
.attr('x2', (d: any) => d.target.x)
.attr('y2', (d: any) => d.target.y);
node.attr('cx', (d: any) => d.x).attr('cy', (d: any) => d.y);
// Update link text position to remain at the midpoint
linkText
.attr('x', (d: any) => (d.source.x + d.target.x) / 2)
.attr('y', (d: any) => (d.source.y + d.target.y) / 2);
});
});
我想要所有节点,并且如果存在来自(A -> B)然后再次来自(B -> A)的连接,我想要单独的链接
在您的示例代码中,d3 实际上是在两个方向上应用链接,但它们是彼此叠置的两条线。 最常被引用的做你想做的事的例子是这个example。 它修改线条并将链接更改为带有曲线的路径,以便它们不会重叠。 它还添加了一个漂亮的箭头标记来指示方向。 这里它被应用到你的代码中(修改为删除 ts 的东西):
<!DOCTYPE html>
<html>
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.9.0/d3.js"></script>
</head>
<body>
<div id="svgContainer"></div>
<script>
const data = {
nodes: [
{ id: 'Alice'},
{ id: 'Bob' },
{ id: 'Carol'},
],
links: [
{ source: 'Alice', target: 'Bob' },
{ source: 'Bob', target: 'Carol' },
{ source: 'Carol', target: 'Bob' },
],
};
const width = 600,
height = 600;
const svg = d3
.select('#svgContainer')
.append('svg')
.attr('width', width)
.attr('height', height);
svg
.append('defs')
.append('marker')
.attr('id', "arrow")
.attr('viewBox', '0 -5 10 10')
.attr('refX', 15)
.attr('refY', -1.5)
.attr('markerWidth', 6)
.attr('markerHeight', 6)
.attr('orient', 'auto')
.append('path')
.attr('d', 'M0,-5L10,0L0,5');
// Modify nodes to ensure they have vx, vy, x, y properties
const nodesWithPositions = data.nodes.map((node) => ({
...node,
vx: 0, // Initialize vx for D3 simulation
vy: 0, // Initialize vy for D3 simulation
x: node.x || 0, // Ensure x is initialized if missing
y: node.y || 0, // Ensure y is initialized if missing
}));
// Create a force simulation using nodes and links
const simulation = d3
.forceSimulation(nodesWithPositions) // Use the updated nodes
.force(
'link',
d3
.forceLink(data.links)
.id((d) => d.id) // Use the correct id function
.distance(200) // Define the link distance
)
.force('charge', d3.forceManyBody().strength(-300)) // Charge force to push nodes apart
.force('center', d3.forceCenter(width / 2, height / 2)); // Center force
// Add links to the SVG and apply the arrow marker
const link = svg
.append('g')
.selectAll('path')
.data(data.links) // Use the original links (no duplication)
.enter()
.append('path')
.attr('fill', 'none')
.attr("marker-end", "url(#arrow)")
.attr('stroke', '#999')
.attr('stroke-opacity', 0.6)
.attr('stroke-width', 2); // Set link stroke width based on cycle count
// Add cycleCount label to the middle of each link
const linkText = svg
.append('g')
.selectAll('text')
.data(data.links)
.enter()
.append('text')
.attr('x', (d) => (d.source.x + d.target.x) / 2) // Calculate midpoint of the link
.attr('y', (d) => (d.source.y + d.target.y) / 2) // Calculate midpoint of the link
.attr('dy', -10) // Move the text slightly above the midpoint
.attr('text-anchor', 'middle')
.attr('font-size', 12)
.attr('fill', '#333')
//.text((d) => d.cycleCount); // Display the cycle count
// Add nodes to the SVG
const node = svg
.append('g')
.selectAll('circle')
.data(nodesWithPositions) // Pass the updated nodes with vx, vy, x, y
.enter()
.append('circle')
.attr('r', 10) // Set the radius of the nodes
.attr('fill', '#69b3a2') // Set the fill color of the nodes
.attr('stroke', '#333') // Set the border color of the nodes
.attr('stroke-width', 1.5); // Set the border width;
// Update link and node positions on each tick
simulation.on('tick', () => {
link.attr('d', linkArc);
node.attr('cx', (d) => d.x).attr('cy', (d) => d.y);
// Update link text position to remain at the midpoint
linkText
.attr('x', (d) => (d.source.x + d.target.x) / 2)
.attr('y', (d) => (d.source.y + d.target.y) / 2);
});
function linkArc(d) {
var dx = d.target.x - d.source.x,
dy = d.target.y - d.source.y,
dr = Math.sqrt(dx * dx + dy * dy);
return (
'M' +
d.source.x +
',' +
d.source.y +
'A' +
dr +
',' +
dr +
' 0 0,1 ' +
d.target.x +
',' +
d.target.y
);
}
</script>
</body>
</html>