D3网络图

问题描述 投票:0回答:1

我创建了一个工作正常的 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)的连接,我想要单独的链接

javascript d3.js graph svelte sveltekit
1个回答
0
投票

在您的示例代码中,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>

最新问题
© www.soinside.com 2019 - 2025. All rights reserved.