I have created a d3 network graph which is working fine, issue is if there is a connection between (A -> B and B -> A) i want separate links for that. How can i do that?
This is what i have done so far
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);
});
});
I want all nodes and i want separate links if there is a connection from (A -> B) and then again from (B -> A)