javascript - D3 network graph - Stack Overflow

admin2025-05-02  1

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)

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)

Share Improve this question asked Jan 2 at 11:56 Faizan AhmadFaizan Ahmad 3925 silver badges17 bronze badges
Add a comment  | 

1 Answer 1

Reset to default 0

In your example code, d3 is actually applying links in both directions, but they are two lines laying on top of each other. The most cited example of doing what you want is this example. It changes the link to a path element with a curve so they don't overlay. It also adds a nice arrow marker to indicate direction. Here it is applied to your code (modified to remove the ts stuff):

<!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 = 200;

      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>

转载请注明原文地址:http://anycun.com/QandA/1746122092a91978.html