74581


d3.js をつかって円グラフをかいてみたんですが  
後半に少ないデータが固まるとラベルが重なってしまいます

その時に少しでも多いデータがはっきりわかるように  
手前に表示したいんですがどうすればいいのでしょうか

[code]

    const g = svg.selectAll(".arc")
        .data(pie(data))
        .enter()
        .append("g")
        .attr('z-index', d => 10 - d.index)
        .attr("class", "arc");
    
[/code]

ここで z-index を指定しても g タグには聞かないみたいです

g.arc の追加順を逆にすればよさそうなのですが

[code]

        .enter()
        .append("g")
    
[/code]

の間に get() とか reverse() とかをはさんでもメソッドがないといわれて  
Selection というオブジェクトみたいで配列に変換したり  
追加順を制御することもできません

[code]

    <html>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
    <script src="https://d3js.org/d3.v5.js"></script>
    <script>
      const data = [
        {legend: '●94%', value: 94, color: '#fcc'},
        {legend: '●3%', value: 3, color: '#ecd'},
        {legend: '●2%', value: 2, color: '#dce'},
        {legend: '●1%', value: 1, color: '#ccf'},
      ];
    
      $('html').append('<svg></svg>');
      const svg = d3.select('svg');
      const pie = d3.pie().value(d => d.value);
      const arc = d3.arc().innerRadius(0);
      let isAnimated = false;
    
      const render = () => {
        const g = svg.selectAll(".arc")
        .data(pie(data))
        .enter()
        .append("g")
        .attr('z-index', d => 10 - d.index)
        .attr("class", "arc");
    
        g.append("path")
        .attr("fill", d => d.data.color )
        .attr("stroke", "white");
    
        g.append("text")
        .attr("stroke", "black")
        .attr("fill", "white")
        .attr("font-weight", "BOLD")
        .attr('text-anchor', 'middle')
        .text((d) => d.data.legend);
      };
    
    
      const update = () => {
        const size = 200;
        arc.outerRadius(size / 2);
    
        svg
          .attr("width", size)
          .attr("height", size);
    
        const g = svg.selectAll(".arc")
        .attr("transform", `translate(${size / 2}, ${size / 2})`);
    
        g.selectAll("text").attr("transform", d => `translate(${arc.centroid(d)})`);
    
      }
    
      const animate = () => {
        const g = svg.selectAll(".arc");
        const length = data.length;
        let i = 0;
    
        g.selectAll("path")
        .transition()
        .attrTween("d", (b) => {
          const interpolate = d3.interpolate(
            {startAngle: 0, endAngle: 0, opacity: 0}, b
          );
          return (t) => {
            if(t >= 1) {
              i++;
              isAnimated = i === length;
            }
            return arc(interpolate(t));
          };
        });
        };
    
      render();
      update();
      animate();
    </script>
    </html>
[/code]


CC BY-SA 4.0
2021-03-10T11:16:47.890
74570
2021-03-10T22:16:16.540
2021-03-10T11:56:32.713
question
javascript d3.js
d3.js のパイチャートでラベルの重なりを逆順にしたい


例えば `data` の要素の順序を `value` をキーにして逆順にしてみると、

[code]

    data.sort((a, b) => {return a.value - b.value});
    
[/code]

以下の様に表示されます。

[![reverse
order](https://i.stack.imgur.com/nmKu3.png)](https://i.stack.imgur.com/nmKu3.png)

表示されている SVG 画像の中身(source code)を確認してみると、

[code]

                       :
    <g class="arc" transform="translate(100, 100)">
    <path fill="#ecd" ...></path>
    <text stroke="black" fill="white" ...>●3%</text>
    </g>
                       :
    
[/code]

となっていて、描画の順序としては最初(`data[0]`)のアークシェイプとそのラベル、次に `data[1]`
のアークシェイプとそのラベル、となります。これは以下の様に、ラベルを `arc` の子ノードとして追加しているためです。

[code]

      const g = svg.selectAll(".arc")
              :
    
      g.append("path")
              :
    
      g.append("text")
              :
    
[/code]

ではどうするのかと言えば、ラベルを `arc` の子ノードから外して `svg` に追加します。その上で、最後にまとめて描画します。

[code]

    <html>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
    <script src="https://d3js.org/d3.v5.js"></script>
    <script>
      const data = [
        {legend: '●94%', value: 94, color: '#fcc'},
        {legend: '●3%', value: 3, color: '#ecd'},
        {legend: '●2%', value: 2, color: '#dce'},
        {legend: '●1%', value: 1, color: '#ccf'},
      ];
    
      data.sort((a, b) => {return a.value - b.value});
    
      $('html').append('<svg></svg>');
      const svg = d3.select('svg');
      const pie = d3.pie().value(d => d.value);
      const arc = d3.arc().innerRadius(0);
      let isAnimated = false;
    
      const render = () => {
        const g = svg.selectAll(".arc")
        .data(pie(data))
        .enter()
        .append("g")
        .attr('z-index', d => 10 - d.index)
        .attr("class", "arc");
    
        g.append("path")
        .attr("fill", d => d.data.color )
        .attr("stroke", "white");
    
        svg.selectAll("text")
        .data(data)
        .enter()
        .append("text")
        .attr("stroke", "black")
        .attr("fill", "white")
        .attr("font-weight", "BOLD")
        .attr('text-anchor', 'middle')
        .text(d => d.legend);
    
      };
    
    
      const update = () => {
        const size = 200;
        arc.outerRadius(size / 2);
    
        svg
          .attr("width", size)
          .attr("height", size);
    
        const g = svg.selectAll(".arc")
        .attr("transform", `translate(${size / 2}, ${size / 2})`);
    
        svg.selectAll("text")
        .attr("transform", (_, i) =>
              `translate(${
                arc.centroid(g.data()[i]).map(v => v + size/2)
              })`);
    
      };
    
      const animate = () => {
        const g = svg.selectAll(".arc");
        const length = data.length;
        let i = 0;
    
        g.selectAll("path")
        .transition()
        .attrTween("d", (b) => {
          const interpolate = d3.interpolate(
            {startAngle: 0, endAngle: 0, opacity: 0}, b
          );
          return (t) => {
            if(t >= 1) {
              i++;
              isAnimated = i === length;
            }
            return arc(interpolate(t));
          };
        });
        };
    
      render();
      update();
      animate();
    </script>
    </html>
[/code]

以降は余録的なものですが、もう少しラベルを見やすくしてみます(表示位置を微調整しています)。

[code]

    <html>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
    <script src="https://d3js.org/d3.v5.js"></script>
    <script>
      const data = [
        {legend: '●94%', value: 94, color: '#fcc'},
        {legend: '●3%', value: 3, color: '#ecd'},
        {legend: '●2%', value: 2, color: '#dce'},
        {legend: '●1%', value: 1, color: '#ccf'},
      ];
    
      $('html').append('<svg></svg>');
      const svg = d3.select('svg');
      const pie = d3.pie().value(d => d.value);
      const arc = d3.arc().innerRadius(0);
      let isAnimated = false;
    
      const render = () => {
        const g = svg.selectAll(".arc")
        .data(pie(data))
        .enter()
        .append("g")
        .attr('z-index', d => 10 - d.index)
        .attr("class", "arc");
    
        g.append("path")
        .attr("fill", d => d.data.color )
        .attr("stroke", "white");
    
        svg.selectAll("text")
        .data(data)
        .enter()
        .append("text")
        .attr("stroke", "black")
        .attr("fill", "white")
        .attr("font-weight", "BOLD")
        .attr('text-anchor', 'middle')
        .text(d => d.legend);
    
      };
    
    
      const update = () => {
        const size = 200;
        arc.outerRadius(size / 2);
    
        svg
          .attr("width", size)
          .attr("height", size);
    
        const g = svg.selectAll(".arc")
        .attr("transform", `translate(${size / 2}, ${size / 2})`);
    
        svg.selectAll("text")
        .attr("transform", (_, i) =>
          `translate(${
            arc.centroid(g.data()[i])
               .map((v, j) => {
                 return size/2 + v + (
                   (g.data()[i].data.value<10) ?
                     (j?((i-1)*30-20):(i*2+5)) : 0)
               })
          })`)
    
      };
    
      const animate = () => {
        const g = svg.selectAll(".arc");
        const length = data.length;
        let i = 0;
    
        g.selectAll("path")
        .transition()
        .attrTween("d", (b) => {
          const interpolate = d3.interpolate(
            {startAngle: 0, endAngle: 0, opacity: 0}, b
          );
          return (t) => {
            if(t >= 1) {
              i++;
              isAnimated = i === length;
            }
            return arc(interpolate(t));
          };
        });
        };
    
      render();
      update();
      animate();
    </script>
    </html>
[/code]


CC BY-SA 4.0
2021-03-10T22:16:16.540
74581
2021-03-10T22:16:16.540
74570
answer