结合 D3 饼图和分层边缘捆绑

Combine D3 Pie Chart and Hierarchical Edge Bundling

如何使用 D3 实现此目的? desired output

两层饼图很简单https://embed.plnkr.co/plunk/2p0zmp

或者使用带有图形和节点的 d3 网络,http://using-d3js.com/05_08_links.html

但我如何将“节点”和“链接”的概念叠加到饼图的这些弧线上?

首选哪种数据结构?

{
  nodes: [
     { 
        layer: 1, 
        data: [
           {name: A },
           {name: B },
           {name: C },
           {name: D }
        ]
     },
     { 
        layer: 2, 
        data: [
           {name: E },
           {name: F },
           {name: G }
        ]
     }
  ],
  links: [{ source: 'B', target: 'E'}, { source: 'D', target: 'F'}]
}

这与您要查找的内容非常接近。您可以使用一些额外的圆弧生成器和 arc.centroid() to get the positions for the start and ends of the links. Then you can use a link generator 来绘制链接。这样做的一个缺点是链接可能会与节点重叠。

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <script src="https://d3js.org/d3.v7.js"></script>
</head>

<body>
    <div id="chart"></div>

    <script>
      /*
        set up
      */

      const width = 700;
      const height = 400;

      const svg = d3.select('#chart')
        .append('svg')
          .attr('width', width)
          .attr('height', height);

      const g = svg.append('g')
          .attr('transform', `translate(${width / 2},${height})`);

      /*
        data
      */

      const level1Nodes = [
        { name: 'A', value: 50, level: 1, color: 'RoyalBlue' },
        { name: 'B', value: 50, level: 1, color: 'DarkOrange' },
        { name: 'C', value: 50, level: 1, color: 'DarkOrange' },
        { name: 'D', value: 30, level: 1, color: 'Gold' }
      ];

      const level2Nodes = [
        { name: 'E', value: 75, level: 2, color: 'RoyalBlue' },
        { name: 'F', value: 75, level: 2, color: 'DarkOrange' },
        { name: 'G', value: 30, level: 2, color: 'RoyalBlue' },
      ];

      const links = [
        { source: 'B', target: 'E'},
        { source: 'D', target: 'F'}
      ];

      /*
        pie generator
      */

      const pie = d3.pie()
          .value(d => d.value)
          .startAngle(-Math.PI / 2)
          .endAngle(Math.PI / 2)
          .padAngle(Math.PI / 45);

      // calculate the angles for the slices of the nodes
      const slices = [
        ...pie(level1Nodes),
        ...pie(level2Nodes)
      ];

      /*
        arcs
      */

      const level1InnerRadius = 130;
      const level1OuterRadius = 200;

      const level2InnerRadius = 270;
      const level2OuterRadius = 340;

      // for drawing the nodes

      const level1Arc = d3.arc()
          .innerRadius(level1InnerRadius)
          .outerRadius(level1OuterRadius);

      const level2Arc = d3.arc()
          .innerRadius(level2InnerRadius)
          .outerRadius(level2OuterRadius);

      const levelToArc = new Map([
        [1, level1Arc],
        [2, level2Arc]
      ]);

      // for positioning the links along the outside
      // of the level 1 nodes and the inside of the
      // level 2 nodes

      const level1OuterArc = d3.arc()
          .innerRadius(level1OuterRadius)
          .outerRadius(level1OuterRadius);

      const level2InnerArc = d3.arc()
          .innerRadius(level2InnerRadius)
          .outerRadius(level2InnerRadius);

      /*
        calculating position of links
      */

      // Map from the name of a node to the data for its arc
      const nameToSlice = d3.index(slices, d => d.data.name);

      // get the start and end positions for each link
      const linkPositions = links.map(({source, target}) => ({
        source: level1OuterArc.centroid(nameToSlice.get(source)),
        target: level2InnerArc.centroid(nameToSlice.get(target)),
      }));

      /*
        drawing
      */

      // nodes
      g.append('g')
        .selectAll('path')
        .data(slices)
        .join('path')
          .attr('d', d => levelToArc.get(d.data.level)(d))
          .attr('fill', d => d.data.color);

      // node labels
      const labelsGroup = g.append('g')
          .attr('font-family', 'sans-serif')
          .attr('font-weight', 'bold')
          .attr('font-size', 30)
        .selectAll('text')
        .data(slices)
        .join('text')
          .attr('dominant-baseline', 'middle')
          .attr('text-anchor', 'middle')
          .attr('transform', d => `translate(${levelToArc.get(d.data.level).centroid(d)})`)
          .text(d => d.data.name);

      // links
      g.append('g')
        .selectAll('path')
        .data(linkPositions)
        .join('path')
          .attr('d', d3.linkVertical())
          .attr('fill', 'none')
          .attr('stroke', 'DarkBlue')
          .attr('stroke-width', 2);

      // circles at the end of links
      g.append('g')
        .selectAll('circle')
        .data(linkPositions.map(({source, target}) => [source, target]).flat())
        .join('circle')
          .attr('r', 5)
          .attr('fill', 'DarkBlue')
          .attr('transform', d => `translate(${d})`);

    </script>
</body>
</html>