You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

D3.js饼图扇区拆分问题:小扇区与大扇区间距重叠

饼图子扇区与父扇区padding重叠问题

我有5个饼图扇区,需要将每个扇区拆分为更小的子扇区,且子扇区间保持1px间距(如示意图中第5个扇区的红色线条所示)。但实现时,子扇区会与父扇区之间的padding发生重叠。

以下是我的代码:

<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.6.1/d3.min.js"></script>
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Indicator test</title>
  <script src="https://d3js.org/d3.v7.min.js"></script>
  <style>
    body,
    html {
      margin: 0px;
      height: 0px;
      font-family: sans-serif;
    }

    .parent {
      display: flex;
      flex-direction: row;
      flex-wrap: wrap;
      justify-content: center;
      align-items: center;
      height: 100vh;
    }

    .child {
      border: 1px solid red
    }

    div.tooltip {
      position: absolute;
      text-align: center;
      padding: 2px;
      background: lightsteelblue;
      border: 0px;
      border-radius: 8px;
    }

    .highlight {
      fill: steelblue;
    }

    .hover {
      fill: #69c !important;
      stroke: #333 !important;
      stroke-width: 2px !important;
    }

    .d3map {
      border: 1px solid red;
    }

    .map-title {
      font-size: 30px
    }

    .map-text {
      text-align: center;
    }

    .map-text-container {
      display: flex;
      flex-direction: column;
      font-size: 14px;
      line-height: 1;
      align-items: center;
      max-width: 155px;
    }
  </style>
</head>


<body>

  <div id="main">
    <div class="parent">
      <div id="d3map" class="child" style="width:100%; height:100%; "></div>
    </div>
  </div>

  <script>

    createMap("d3map")

    function createMap(divId) {

      const mainData = {
        cities: []
      }

      const svgDiv = document.getElementById(divId).getBoundingClientRect();
      let width = 600;
      let height = 600;
      const radius = width / 2

      const svg = d3
        .select("#" + divId)
        .append("svg")
        .attr("viewBox", `0 0 ${width} ${height}`)

      ready(null, randomData())
      async function ready(error, data) {

        const numberOfPies = 5
        console.log("123", numberOfPies);

        svg.append("circle")
          .style("stroke", "rgba(0,0,0,0.2)")
          .style("stroke-width", "1")
          .style("fill", "rgba(0,0,0,0)")
          .attr("r", radius - 1)
          .attr("cx", width / 2)
          .attr("cy", height / 2);

        const sectors = [...Array(numberOfPies)].map((i, key) => {
          return {
            name: key, size: 10,
          }
        });
        console.log('pie(sectors): ', sectors);

        const arc = (offset = 5) => {
          return d3.arc()
            .outerRadius(radius - offset)
            .innerRadius(radius)
        }

        const labelArc = d3.arc()
          .innerRadius(150)
          .outerRadius(radius)

        const pie = d3.pie()
          .startAngle(-90 * Math.PI / 180)
          .endAngle(-90 * Math.PI / 180 + 2 * Math.PI)
          .value(1)
          .padAngle(.05) // space between pies
          .sort(null);

        const lineFunc = d3.line()
          .x(function (d) { return d.x })
          .y(function (d) { return d.y })


        const labels = svg.append('g')
          .attr("class", "labels")
          .attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");

        labels.selectAll('.pies')
          .data(pie(sectors))
          .enter()
          .append('path')
          .attr('d', labelArc)
          .attr('class', "pies")
          .attr("stroke", "none")
          .attr("fill", d => "gray")
          .style("stroke-width", "1px")
          .style("opacity", d => {
            return d.data.skipThisSector ? 0 : 1
          })


        labels.selectAll('.lines')
          .data(pie(sectors))
          .enter()
          .append('path')
          .attr('d', d => {
            console.log('d: ', d);

            const pies = d3.pie()
              .startAngle(d.startAngle)
              .endAngle(d.endAngle)
              .value(1)
              .padAngle(.01) // space between pies
              .sort(null);

            labels.selectAll('.piesss')
              .data(pies(randomData()))
              .enter()
              .append('path')
              .attr('d', labelArc)
              .attr('class', "pies")
              .attr("stroke", "none")
              .attr("fill", d => "red")
              .style("stroke-width", "1px")
              .style("opacity", "0.4")


          })

        labels.selectAll(".labelsText")
          .data(pie(sectors))
          .enter()
          .append("text")
          .attr("dx", d => {
            const [x, y] = labelArc.centroid(d)
            return x
          })
          .attr("dy", d => {
            const [x, y] = labelArc.centroid(d)
            return y + 5
          })
          .text(function (d) {
            if (!d.data.skipThisSector) {
              return d.data.name
            } else {
              return
            }
          })
          .style("font-size", "8px")
          .style("text-anchor", "middle")
          .attr("transform", function (d) {
            var locationData = this.getBBox();
            var x = locationData.x + (locationData.width / 2);
            var y = locationData.y + (locationData.height / 2);
            let angle = (Math.atan2(y, x) * 180) / Math.PI;

            if (angle > 90) angle = 180 + angle
            if (angle < -90 && angle > -180) angle = 180 + angle
            var result = 'translate(' + x + ',' + y + ')';
            result += `rotate(${angle})`;
            result += 'translate(' + (-x) + ',' + (-y) + ')';

            return result;
          })
          .style("text-anchor", function (d) {
            var locationData = this.getBBox();
            var x = locationData.x + (locationData.width / 2);
            var y = locationData.y + (locationData.height / 2);
            let angle = (Math.atan2(y, x) * 180) / Math.PI;
            if (angle > 90) return "end"
            if (angle < -90 && angle > -180) return "end"
            return "start"
          })
          .attr("fill", d => {
            return "white"
          })
      }
    }

    function randomData() {

      return [...Array(Math.floor(Math.random() * 11))].map((i, key) => {
        return {
          name: key, size: 10,
        }
      });

    }


    const delay = ms => new Promise(res => setTimeout(res, ms));
  </script>
</body>

</html>

效果示意图:
饼图效果示意图

内容的提问来源于stack exchange,提问作者SERG

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.14 16:11:16