Комбинируйте круговую диаграмму D3 и Иерархическое объединение ребер

#javascript #d3.js #d3pie.js

Вопрос:

Как мне добиться этого с помощью D3? желаемый результат

Легко иметь два слоя круговых диаграмм https://embed.plnkr.co/plunk/2p0zmp

Или использовать сеть d3 с графом и узлами, http://using-d3js.com/05_08_links.html

но как я мог наложить понятие «узлы» и «ссылки» на эти дуги piechart?

Какая структура данных предпочтительнее?

 {
  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'}]
}
 

Ответ №1:

Это довольно близко подходит к тому, что вы ищете. Вы можете использовать некоторые дополнительные генераторы дуги и arc.centroid (), чтобы получить позиции для начала и конца ссылок. Затем вы можете использовать генератор ссылок для создания ссылок. Одним из недостатков этого является то, что ссылки могут перекрывать узлы.

 <!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>