使用 .getBBox() 方法向 JavaScript 中的 SVG 路径形状添加编号

Adding numbering to SVG path shapes in JavaScript using .getBBox() method

我正在尝试将数字放入自定义形状中。我正在使用 getBBox() 来查找形状的中心。它仅适用于某些形状,因为中心点可能不在空心形状内。

Codepen 上的演示: https://codepen.io/arindamx01/pen/KKyLNrj?editors=1010
这是我的 JavaScript 代码:

    let colorGroup = ['#700843', '#E95C87', '#AED9C5', '#FDFCFD', '#F8AF3F'];
    colorGroup.forEach( function(e, i) { 
    console.log(e, i);
    $(`path[data-color-group = "${e}"]`).each(function(){
        let pathBBox = $(this)[0].getBBox();
        console.log(pathBBox, $(this)[0])
        let getPathData = $(this).attr("data-index");

        var svgNS = "http://www.w3.org/2000/svg";
        var newText = document.createElementNS(svgNS,"text");
        newText.setAttributeNS(null,"x", pathBBox.x + pathBBox.width/2);     
        newText.setAttributeNS(null,"y", pathBBox.y + pathBBox.height/2);                    
        newText.setAttributeNS(null,"font-size","10");
        var textNode = document.createTextNode(i+1);
        newText.appendChild(textNode);
        document.getElementById("myIdInfo").appendChild(newText); 
    });
});

我尝试使用 getBBox() 方法,但它不适用于所有形状。

在上图中,数字 4 应该在空心的 C 形状内。

每个形状都不同,盒子的中心可能不是实际形状。对于那些空心形状,没有精确的中心。你只需要找到落在形状内的任何合适的点。

以盒子的中心为原点,我们可以尝试找到与 x 轴和 y 轴相交的点。并在两个交点之间选择一个点:
codesandbox 上的演示。加载需要一些时间。

    let sv = document.querySelector('svg');
    let colorGroup = ['#700843', '#E95C87', '#AED9C5', '#FDFCFD', '#F8AF3F'];
    colorGroup.forEach(function (e, i) {
      $(`path[data-color-group = "${e}"]`).each(function (ii, aa) {
        let path = $(this)[0]

        if (i == 3) {
          path.setAttribute('style', `fill: #cf0acf`);
        } else if (i == 0) {
          path.setAttribute('style', `fill: #a35a83`);
        } else {
          path.setAttribute('style', `fill: ${e}`);
        }

        //console.log(pathBBox, path);
        let getPathData = $(this).attr("data-index");

        point = getInsidePoint(path);


        var svgNS = "http://www.w3.org/2000/svg";
        var newText = document.createElementNS(svgNS, "text");
        newText.setAttributeNS(null, "x", point.x);
        newText.setAttributeNS(null, "y", point.y);
        newText.setAttributeNS(null, "font-size", "10");
        var textNode = document.createTextNode(i + 1);
        newText.appendChild(textNode);
        document.getElementById("myIdInfo").appendChild(newText);
      });
    });

    // figure out point insice the shape
    function getInsidePoint(path) {
      let pathBBox = path.getBBox();
      let point = sv.createSVGPoint();
      point.x = Math.floor(pathBBox.x + pathBBox.width / 2);
      point.y = Math.floor(pathBBox.y + pathBBox.height / 2);

      // if mid point is inside then return
      if (path.isPointInFill(point)) {
        return point;
      }

      let result = sv.createSVGPoint();
      let l = path.getTotalLength();
      let h = [];  // horizontal intersections
      let v = [];  // vertical intersections

      // find intersecting points along x and y axis
      for (let i = 0; i < l; i++) {
        let p = path.getPointAtLength(i);
        p.x = Math.floor(p.x)
        p.y = Math.floor(p.y);

        if (p.x == point.x) v.push(p);
        if (p.y == point.y) h.push(p);
      }

      // whichever axis has less intersecitions 
      // get center point of the intersection
      try {
        if (h.length < v.length) {
          result.y = point.y;
          // find out which point in between two intersection points falls inside
          for (let i = 0; i < h.length - 1; i++) {
            result.x = Math.abs(h[i + 1].x - h[i].x) / 2;
            if (result.x < 2) continue;
            result.x += Math.min(h[i + 1].x, h[i].x);
            if (path.isPointInFill(result)) {
              break;
            }
          }
        } else {
          result.x = point.x;
          for (let i = 0; i < v.length - 1; i++) {
            result.y = Math.abs(v[i + 1].y - v[i].y) / 2;
            if (result.y < 2) continue;
            result.y += Math.min(v[i + 1].y, v[i].y);
            if (path.isPointInFill(result)) {
              break;
            }
          }
        }
      } catch (e) {
        // ignore errors for dots and open shapes
      }

      return result;
    } 

外观如下:

注意,使用 getInsidePoint 函数调整数字位置。这不是最有效的实施方式,请参阅 答案以获得更有效的实施方法。


可能的改进可能是:

  • 为每个形状手动找出更好的点并将它们放入路径元素的 data-* 属性中:
<path data-label-x='10' data-label-y='40' ...>...</path>

并使用这些 co-ordinates。无需每次都在客户端计算。

  • 如果图表是可缩放的,则标记相似的形状。并且只对一种形状进行计算,并在考虑旋转的情况下对所有相似形状使用相同的点。

由于您的图形(曼陀罗)具有径向布局,您也可以尝试找到基于角度的交叉点。

我们可以假设没有填充相交中心的形状将在中心周围有一些交点 x/y。

本质上,我们还使用 isPointInFill() 首先排除形状填充区域内已经很好的坐标,然后为所有例外找到合适的(x/y 偏移)替代方案。

这种方法提供了一些“最佳匹配”逻辑——即更喜欢与更多周围环境相交的坐标 space。上面用绿点表示:更多交叉点(array.length 为基础)将优先计算标签的 x/y 坐标。

我们可以通过仅分析围绕形状中心(由 getBBox() 检索)的一些可预测且优先(基于角度)的点来大幅减少“检查点”的数量。

由于图形的复杂性,在性能方面仍然相当昂贵

中心周围的示例检查点:

let svg = document.querySelector('svg');
let labelGroup = svg.querySelector('#myIdInfo');
let colorGroup = ['#CF0ACF', '#E95C87', '#AED9C5', '#FDFCFD', '#F8AF3F'];
let decimals = 1;
let labelGroupMarkup = '';
//get svg viewBox dimensions for rotation checks;
let svgBB = svg.getAttribute('viewBox').split(' ');
let [svgCx, svgCy] = [(svgBB[0] + svgBB[2]) / 2, (svgBB[1] + svgBB[3] / 2)];

// count point checks for debugging
let checks = 0;
let pointHits = 0;
let notInFill = 0;

// loop colors 
colorGroup.forEach(function(color, colorIndex) {
  // find shapes 
  let shapes = svg.querySelectorAll('[data-color-group="' + color + '"] path');
  shapes.forEach(function(shape, i) {
    let bb = shape.getBBox();
    let [x, y, w, h] = [bb.x, bb.y, bb.width, bb.height];
    let cx = +(x + w / 2).toFixed(decimals);
    let cy = +(y + h / 2).toFixed(decimals);
    let l = +(x).toFixed(decimals);
    let r = +(x + w).toFixed(decimals);
    let t = +(y).toFixed(decimals);
    let b = +(y + h).toFixed(decimals);

    // label coordinates in fill?
    let inFill = checkPointIntersection(svg, shape, [cx, cy]);
    checks++;

    //not in fill: find alternative coordinates
    if (!inFill) {
      notInFill++; //
      let rMid = (w + h) / 2;
      let angles = [0, 45, 90, 135, 180, 225, 270, 315];

      // check quarter position to reduce angle checkpoints
      let classRotate = '';
      if (t < svgCy && b < svgCy && l < svgCx && r > svgCx) {
        classRotate = 'topCenter';
        angles = [0, 180]
      } else if (l > svgCx && b < svgCy) {
        classRotate = 'topRight';
        angles = [45, 90];
      } else if (l > svgCx && b > svgCy && t < svgCy && b > svgCy) {
        classRotate = 'centerRight';
        angles = [90, 0]
      } else if (l > svgCx && t > svgCy) {
        classRotate = 'bottomRight';
        angles = [135, 180];
      } else if (l < svgCx && t > svgCy && r > svgCx) {
        classRotate = 'bottomCenter';
        angles = [180, 0];
      } else if (r < svgCx && t > svgCy) {
        classRotate = 'bottomLeft';
        angles = [225, 270];
      } else if (r < svgCx && b > svgCy) {
        classRotate = 'centerLeft';
        angles = [270, 0];
      } else if (r < svgCx && b < svgCy) {
        classRotate = 'topLeft';
        angles = [315, 270];
      } else {
        classRotate = 'center';
        angles = [0];
      }
      //shape.classList.add(classRotate);

      let labeCoords = checkCircleIntersects(svg, shape, angles, cx, cy, rMid / 4);
      cx = labeCoords[0]
      cy = labeCoords[1]
    }

    let value = colorIndex;
    labelGroupMarkup +=
      `<text x="${cx}" y="${cy}" dy="2" >${value}</text>`;

    //test eventListener: set colors by color group
    shape.addEventListener('click', function(e) {
      let current = e.currentTarget;
      let g = current.closest('[data-color-group]');
      let color = g.getAttribute('data-color-group');
      let shapeColor = current.getAttribute('fill');
      if (!shapeColor) {
        current.setAttribute('fill', color)
      } else {
        current.removeAttribute('fill')
      }
    })

  })
  // add labels
  labelGroup.innerHTML = labelGroupMarkup;
});

console.log('totalchecks: ' + checks + ' notInFill: ' + notInFill + ' pointHits: ' + pointHits)


// helpers
function checkCircleIntersects(svg, shape, percentages, cx, cy, r, percentageToAngel = false) {
  let interpoints = [];
  let interpointspercent = [];
  percentages.forEach(function(percent) {
    // check intersections on for different radii
    let pArr = [];
    let steps = [0.75, 1, 1.25, 1.5, 1.75, 2, 2.25];
    for (let i = 0; i < steps.length; i++) {
      let p = getPosOnCircle(cx, cy, percent, r * steps[i], percentageToAngel);
      let inFill = checkPointIntersection(svg, shape, p);
      checks++;

      if (inFill) {
        pArr.push(p);
        pointHits++;
      }
    }
    // add matches to array
    interpointspercent.push(pArr);
  });

  // return best match x/y coordinates
  let bestMatchArr = getBestMatchArr(interpointspercent);
  let bestMatch = getMidPoints(bestMatchArr);

  if (!bestMatch.length) {
    shape.setAttribute('fill', 'red')
    console.log(`no match: ${cx} ${cy}`)
    bestMatch = [cx, cy]
  }
  return bestMatch
}

function checkPointIntersection(svg, shape, coords, strokeIntersection = false) {
  let svgP = svg.createSVGPoint();
  svgP.x = coords[0];
  svgP.y = coords[1];
  let inFill = shape.isPointInFill(svgP);
  let inStroke = strokeIntersection ? shape.isPointInStroke(svgP) : false;
  //let inStroke = shape.isPointInStroke(svgP);
  let intersecting = false;
  if (inFill && !inStroke) {
    intersecting = true;
  }
  return intersecting;
}


// helper: get x/y coordinates according to angle or percentage
function getPosOnCircle(cx, cy, value, radius, valueToPercent = false, decimals = 3, offset = -90) {
  // convert percentages to angles 
  let angle = valueToPercent ? 360 / (100 / value) + offset : value + offset;
  let x = +(cx + Math.cos((angle * Math.PI) / 180) * radius).toFixed(decimals);
  let y = +(cy + Math.sin((angle * Math.PI) / 180) * radius).toFixed(decimals);
  return [x, y];
}

// find longest intersection array
function getBestMatchArr(arrays) {
  let max = 0;
  let longestArr = [];
  for (let i = 0; i < arrays.length; i++) {
    let current = arrays[i];
    let len = current.length;
    if (len > max) {
      max = len;
    }
  }
  for (let i = 0; i < arrays.length; i++) {
    let current = arrays[i];
    let len = current.length;
    if (len == max) {
      longestArr.push(current);
    }
  }
  let midIndex = longestArr.length > 1 ? Math.floor((longestArr.length - 1) / 2) : 0;
  return longestArr[midIndex];
}


// interpolate first and last x/y
function getMidPoints(coords) {
    let l = coords.length - 1;
    let midIndex = Math.floor(l / 2);
    let [x1, y1] = coords[0];
    let [x2, y2] = coords[coords.length - 1];
    let middle = [  +((x1 + x2) / 2).toFixed(1)  , +((y1 + y2) / 2).toFixed(1) ];
    return middle;
}


// debug helper: render coordinates as markers
function renderPoint(svg, coords, fill = 'red', r = '2') {
  let marker =
    '<circle cx="' +
    coords[0] +
    '" cy="' +
    coords[1] +
    '"  r="' + r + '" fill="' + fill + '" ><title>' +
    coords.join(", ") +
    "</title></circle>";
  svg.insertAdjacentHTML("beforeend", marker);
}
svg {
  width: 50em;
  border: 1px solid #ccc;
  display: block;
}

text {
  font-family: Arial;
  font-weight: bold;
  font-size: 6px;
  text-shadow: -0.1em -0.1em 0 #fff, -0.1em 0 0 #fff, 0em -0.1em 0 #fff, 0em 0.1em 0 #fff, 0.1em -0.1em 0 #fff, -0.1em 0.1em 0 #fff, 0.1em 0.1em 0 #fff;
}
<svg viewBox="0 0 1144.4 1144.4">
<g class="wrap-stroke" stroke-width="1" stroke="#000" fill="#fff">
<g data-color-group="#E95C87" data-fillable="true">
<path d="M572.2 381.5c-14.5 0-27.7 3-37 15.2-7 9.2-9.4 20.5-7.7 31.7a65 65 0 0 1-18.7-17.1c5.3-21.6 25-35.2 37.9-52.3a172.1 172.1 0 0 0 25.5-49 172.1 172.1 0 0 0 25.5 49c12.9 17.1 32.5 30.7 37.9 52.3a65 65 0 0 1-18.7 17.1 41.3 41.3 0 0 0-7.7-31.7c-9.3-12.2-22.5-15.2-37-15.2z"/>
<path d="M707 437.3c-10.2-10.2-21.7-17.4-36.9-15.4a41.3 41.3 0 0 0-27.8 17c-2-8.3-2.4-16.9-1.1-25.3 19-11.5 42.6-7.2 63.8-10.2 18.4-2.6 36.1-8.4 52.6-16.6-8.2 16.6-14 34.2-16.6 52.6-3 21.2 1.3 44.7-10.2 63.8-8.4 1.3-17 .9-25.3-1.1a41.9 41.9 0 0 0 17-27.8c1.9-15.3-5.3-26.7-15.5-37z"/>  
<path d="M437.4 437.3C427.2 447.5 420 459 422 474.2a41.3 41.3 0 0 0 17 27.8c-8.3 2-16.9 2.4-25.3 1.1-11.5-19-7.2-42.6-10.2-63.8a173.5 173.5 0 0 0-16.6-52.6c16.6 8.2 34.2 14 52.6 16.6 21.2 3 44.7-1.3 63.8 10.2 1.3 8.4.9 17-1.1 25.3a41.9 41.9 0 0 0-27.8-17c-15.3-1.9-26.7 5.3-37 15.5z"/>
<path d="M572.2 115.6a18.6 18.6 0 0 0-15 30 20.6 20.6 0 0 0-.5 27.9 20.6 20.6 0 0 0-6.5 23 85.8 85.8 0 0 0-33.4-7.7c-6.5-19.9-11.2-40.6 3.3-58.3a65.2 65.2 0 0 1 52.1-23.5c20.3 0 39 7.5 52.1 23.5 14.5 17.7 9.8 38.3 3.3 58.3-11.5.3-22.9 3-33.4 7.7 3.2-8.6.2-17.5-6.5-23 7.2-8 7-20.2-.5-27.9 9.3-12.3 1.2-30-15-30z"/>
<path d="M895 249.3a18.6 18.6 0 0 0-31.9 10.6c-9.9.1-19.2 7.7-20.1 19.4-8.6-.8-17 3.4-20.8 11.7a88 88 0 0 0-18.1-29.1c9.5-18.7 20.7-36.6 43.6-38.9 20.6-2 39.1 5.9 53.4 20.2a65.7 65.7 0 0 1 20.2 53.4c-2.3 22.8-20.2 34-38.9 43.6a86.3 86.3 0 0 0-29.1-18.1c8-3.7 12.7-12.1 11.7-20.8a20.3 20.3 0 0 0 19.4-20.1 18.5 18.5 0 0 0 10.6-31.9z"/>
<path d="M249.4 249.3a18.6 18.6 0 0 0 10.6 31.9c.1 9.9 7.7 19.2 19.4 20.1-.8 8.6 3.4 17 11.7 20.8a88 88 0 0 0-29.1 18.1c-18.7-9.5-36.6-20.7-38.9-43.6a65.6 65.6 0 0 1 20.2-53.4 65.6 65.6 0 0 1 53.4-20.2c22.8 2.3 34 20.2 43.6 38.9a86.3 86.3 0 0 0-18.1 29.1 20.4 20.4 0 0 0-20.8-11.7 20.3 20.3 0 0 0-20.1-19.4 18.5 18.5 0 0 0-31.9-10.6z"/>
<path d="M572.2 472.3c-3.8-9.5-9.8-18-17.6-24.6-5.8-7.8-10-16.5-7.3-26.5 3-11.1 13.9-16.3 24.9-16.2 11-.2 21.9 5.1 24.9 16.2 2.7 10-1.5 18.7-7.3 26.5a62.7 62.7 0 0 0-17.6 24.6z"/>
</g>
<g data-color-group="#AED9C5" data-fillable="true">
<path d="M572.2 268.5a17.8 17.8 0 0 0-17.9 17.7c0 8.4 6 15.3 13.8 17.2a136.8 136.8 0 0 1-29.8 48.9 33 33 0 0 1-21.5-29.7 70.6 70.6 0 0 1 11.8-40.6c12.9-19.5 31.3-34.6 43.5-54.6 12.2 20 30.6 35.1 43.5 54.6a69.1 69.1 0 0 1 11.8 40.6 33 33 0 0 1-21 29.5l-.5.2a136.8 136.8 0 0 1-29.8-48.9 18 18 0 0 0 13.8-17.2c.2-9.8-8-17.8-17.7-17.7z"/>
<path d="M786.9 357.4c-6.8-6.9-18.3-7-25.1-.2a18 18 0 0 0-2.4 21.9c-17.4 8.4-36.4 13-55.7 13.5l-.2-.5a33 33 0 0 1 6-35.7 68.6 68.6 0 0 1 37.1-20.3c23-4.7 46.6-2.3 69.4-7.8-5.5 22.8-3.2 46.5-7.8 69.4a70.3 70.3 0 0 1-20.3 37.1 33 33 0 0 1-35.7 6l-.5-.2c1.4-19.3 4.3-38.4 13.5-55.7 6.8 4.2 16 3.5 21.9-2.4 6.8-7 6.8-18.2-.2-25.1z"/>
<path d="M357.5 357.4c-6.9 6.8-7 18.3-.2 25.1a18 18 0 0 0 21.9 2.4c8.4 17.4 13 36.4 13.5 55.7l-.5.2a33 33 0 0 1-35.7-6 68.6 68.6 0 0 1-20.3-37.1c-4.7-23-2.3-46.6-7.8-69.4 22.8 5.5 46.5 3.2 69.4 7.8a70.3 70.3 0 0 1 37.1 20.3 33 33 0 0 1 6 35.7l-.2.5c-19.3-.5-38.3-5.1-55.7-13.5 4.2-6.8 3.5-16-2.4-21.9-7-6.8-18.2-6.7-25.1.2z"/>
</g>
<g data-color-group="#FDFCFD" data-fillable="true">
<path d="M572.2 402.4c-11.1 0-23.3 5.1-27.3 16.2-3 8.2-1 16.3 3 23.5-5.1-4-11.2-7.7-17.2-11.6a39 39 0 0 1 8.4-34.2c9.1-10 20.4-12.1 33.2-12.1s24.1 2.1 33.2 12.1a39 39 0 0 1 8.4 34.2c-6 3.9-12.1 7.6-17.2 11.6 3.9-7.2 6-15.3 3-23.5-4.2-11.1-16.3-16.2-27.5-16.2z"/>
<path d="M692.2 452.1c-7.9-7.9-20-12.8-30.8-7.9a27.8 27.8 0 0 0-14.5 18.7c-.8-6.4-2.5-13.4-3.9-20.4a38.9 38.9 0 0 1 30.1-18.2c13.5-.7 23 5.9 32 14.9s15.6 18.5 14.9 32c-.6 12.5-8 23.4-18.2 30.1-7-1.5-14-3.1-20.4-3.9 7.9-2.3 15.1-6.6 18.7-14.5 5-10.8 0-22.9-7.9-30.8z"/>
<path d="M452.2 452.1c-7.9 7.9-12.8 20-7.9 30.8a27.8 27.8 0 0 0 18.7 14.5c-6.4.8-13.4 2.5-20.4 3.9a38.9 38.9 0 0 1-18.2-30.1c-.7-13.5 5.9-23 14.9-32s18.5-15.6 32-14.9c12.5.6 23.4 8 30.1 18.2-1.5 7-3.1 14-3.9 20.4-2.3-7.9-6.6-15.1-14.5-18.7-10.8-5-22.9 0-30.8 7.9z"/>
<path d="M563.7 551.7a27 27 0 0 1 17 0c5.2 2.6 9.4 6.8 12 12 .9 2.8 1.4 5.7 1.4 8.5s-.5 5.7-1.4 8.5a26.8 26.8 0 0 1-12 12 27 27 0 0 1-17 0 26.8 26.8 0 0 1-12-12c-.9-2.8-1.4-5.7-1.4-8.5s.5-5.7 1.4-8.5c2.6-5.2 6.8-9.4 12-12zm8.5 5.8a16 16 0 0 0-5.3.9l-.3.1-.3.1a17.1 17.1 0 0 0-7.6 7.6l-.1.3-.1.3a16 16 0 0 0 0 10.6l.1.3.1.3a17.1 17.1 0 0 0 7.6 7.6l.3.1.3.1a16 16 0 0 0 10.6 0l.3-.1.3-.1a17.1 17.1 0 0 0 7.6-7.6l.1-.3.1-.3a16 16 0 0 0 0-10.6l-.1-.3-.1-.3a17.1 17.1 0 0 0-7.6-7.6l-.3-.1-.3-.1a16 16 0 0 0-5.3-.9z"/>
<path d="M572.2 210.7a93.6 93.6 0 0 0-13.1-9.6l-.4-.7c-10.7-12-1-27.1 13.5-27.1s24.2 15.1 13.5 27.1l-.4.7c-4.6 2.8-9 6-13.1 9.6z"/>
<path d="M827.7 316.6a86 86 0 0 0-2.5-16l.2-.8c.9-16 18.4-19.9 28.7-9.6 10.3 10.3 6.5 27.8-9.6 28.7l-.8.2a78.4 78.4 0 0 0-16-2.5z"/>
<path d="M316.7 316.6a86 86 0 0 0-16 2.5l-.8-.2c-16-.9-19.9-18.4-9.6-28.7s27.8-6.5 28.7 9.6l.2.8a78.4 78.4 0 0 0-2.5 16z"/>
<path d="M572.2 168.1c-4.1 0-7.8.9-11 2.5a15 15 0 0 1 11-25.9 15 15 0 0 1 11 25.9 26 26 0 0 0-11-2.5z"/>
<path d="M857.9 286.5c-2.7-2.7-6-4.8-9.6-6a15 15 0 0 1 26.1-10.5 15 15 0 0 1-10.5 26.1 24.8 24.8 0 0 0-6-9.6z"/>
<path d="M286.6 286.5c-2.7 2.7-4.8 6-6 9.6a15 15 0 0 1-10.5-26.1 15 15 0 0 1 26.1 10.5c-3.7 1.2-7 3.2-9.6 6z"/>
<path d="M572.2 139.4a22 22 0 0 0-10.9 2.9 13.4 13.4 0 0 1 10.9-21.5 13.3 13.3 0 0 1 10.9 21.5 22 22 0 0 0-10.9-2.9z"/>
<path d="M878.1 266.2a21.2 21.2 0 0 0-9.8-5.7 13.3 13.3 0 0 1 22.9-7.5 13.3 13.3 0 0 1-7.5 22.9c-.8-3.4-2.6-6.8-5.6-9.7z"/>
<path d="M266.3 266.2a21.2 21.2 0 0 0-5.7 9.8 13.3 13.3 0 0 1-7.5-22.9 13.3 13.3 0 0 1 22.9 7.5c-3.4.8-6.8 2.6-9.7 5.6z"/>
<path d="m572.2 298.5-1.3-.1c-13.2-1.3-12.7-22.2 1.3-22.1 14-.1 14.5 20.8 1.3 22.1l-1.3.1z"/>
<path d="m765.7 378.6-.9-1c-8.4-10.3 6.7-24.6 16.5-14.7 10 9.8-4.4 25-14.7 16.5-.3-.1-.6-.4-.9-.8z"/>
<path d="m378.7 378.6-1 .9c-10.3 8.4-24.6-6.7-14.7-16.5 9.8-10 25 4.4 16.5 14.7-.1.3-.4.6-.8.9z"/>
<path d="M572.2 68c-14.6 0-14.5-22.3 0-22.1 14.5-.2 14.6 22.1 0 22.1z"/>
<path d="M928.7 215.6c-10.3-10.3 5.5-26 15.7-15.7 10.2 10.2-5.4 26-15.7 15.7z"/>
<path d="M215.7 215.6c-10.3 10.3-26-5.5-15.7-15.7 10.2-10.2 26 5.4 15.7 15.7z"/>
<path d="M472 330.1c18.2 14.1 38.9 23 61.3 27.9l-.4.4c-4.3 4.8-8.6 9.5-12.8 14.4a130.5 130.5 0 0 1-37.8-20.8c-.5-.4-1-.4-1.4-.3-.4.2-.8.6-.8 1.2a133.7 133.7 0 0 1-12 41.5c-6.4-.5-12.8-.8-19.2-1.1h-.5a153.1 153.1 0 0 0 23.6-63.2z"/>
<path d="M672.4 330.1c3.1 22.5 11.1 44 23.6 63h-.5c-6.4.4-12.8.6-19.2 1.1a128.5 128.5 0 0 1-12-41.5c-.1-.6-.4-1-.8-1.2s-1-.1-1.4.3c-11.4 9-24.2 16.1-37.9 20.8-4.1-4.9-8.5-9.6-12.8-14.4l-.3-.4a147.9 147.9 0 0 0 61.3-27.7z"/>
<path d="M461.6 305c15.6 12.1 33 20.9 51.8 26.8 2.9 9.7 9 18.1 18.6 23.2-21.9-5-42.4-14-60.1-28.2-.5-.4-1-.4-1.4-.3-.4.2-.8.6-.8 1.2a149 149 0 0 1-22.5 62.5c3-9.9 1.8-20.6-3.2-29.6a168 168 0 0 0 17.6-55.6z"/>
<path d="M682.9 305a163 163 0 0 0 17.7 55.6c-5 9-6.2 19.7-3.2 29.6-12-19-20.1-39.9-22.5-62.5-.1-.6-.4-1-.8-1.2-.4-.2-1-.1-1.4.3a146.6 146.6 0 0 1-60.1 28.2 39 39 0 0 0 18.7-23.2 164.9 164.9 0 0 0 51.6-26.8z"/>
<path d="M443.3 260.8a177.2 177.2 0 0 0 72.4 31.8h.1l-1 2.5a67 67 0 0 0-3 12.5 170.3 170.3 0 0 1-60.7-31.2c-.5-.4-1-.4-1.4-.3-.4.2-.8.6-.8 1.2a174 174 0 0 1-20.9 65 65.1 65.1 0 0 0-11-6.8l-2.5-1.1.1-.1c15.1-22.1 24.6-47 28.7-73.5z"/>
<path d="M701.2 260.8a177.4 177.4 0 0 0 28.7 73.7l.1.1-2.5 1.1a65.1 65.1 0 0 0-11 6.8 174 174 0 0 1-20.9-65c-.1-.6-.4-1-.8-1.2-.4-.2-1-.1-1.4.3a177.9 177.9 0 0 1-60.7 31.2c-.6-4.3-1.5-8.5-3-12.5l-1-2.5h.1a181 181 0 0 0 72.4-32z"/>
<path d="M451.2 279.9c18.1 14.1 38.4 24 60.4 30.4l-.3 5.1c-.1 4.5.3 9 1.2 13.3-18.6-6-35.7-14.8-51-27.1-.5-.4-1-.4-1.4-.3-.4.2-.8.6-.8 1.2a158 158 0 0 1-16.9 55.3 52.4 52.4 0 0 0-8.5-10.3l-3.8-3.4c11-19.9 18.1-41.7 21.1-64.2-.1 0 0 0 0 0z"/>
<path d="M693.3 279.9a178.1 178.1 0 0 0 21.2 64.2 59.5 59.5 0 0 0-12.3 13.7 158 158 0 0 1-16.9-55.3c-.1-.6-.4-1-.8-1.2-.4-.2-1-.1-1.4.3a157.7 157.7 0 0 1-51 27.1c1.5-6 1.8-12.3.9-18.4a177.7 177.7 0 0 0 60.3-30.4z"/>
<path d="M482.4 355.2c11 8.5 23 14.9 36 19.6-3.4 4.1-6.6 8.4-9.4 13-5.7-3.1-11.2-6.7-16.3-10.8-.5-.4-1-.4-1.4-.3-.4.2-.8.6-.8 1.2-.7 6.5-2 12.9-3.8 19.2a121 121 0 0 0-15.8-2.6c5.7-12.5 9.7-25.5 11.5-39.3z"/>
<path d="M662 355.2c1.8 13.6 5.7 26.9 11.6 39.3-5.4.5-10.6 1.3-15.8 2.6-1.8-6.3-3.1-12.6-3.8-19.2-.1-.6-.4-1-.8-1.2-.4-.2-1-.1-1.4.3-5.1 4.1-10.5 7.7-16.3 10.8-2.8-4.6-6-8.9-9.4-13 13-4.7 24.9-11.1 35.9-19.6z"/>
<path d="M492.8 380.4c4.7 3.6 9.6 6.9 14.8 9.8-2.3 4-4.2 8.2-5.6 12.6a66.2 66.2 0 0 0-12.8-4.9c1.6-5.7 2.8-11.6 3.6-17.5z"/>
<path d="M651.6 380.3c.7 5.9 1.9 11.7 3.6 17.4a66.2 66.2 0 0 0-12.8 4.9 64.5 64.5 0 0 0-5.6-12.6c5.2-2.7 10.1-6 14.8-9.7z"/>
</g>
<g data-color-group="#F8AF3F" data-fillable="true">
<path d="M572.2 98.6c-4 0-7.9.3-11.8.9 2.2-4.1 2.2-8.9.4-13.7-4.8-12.8-18.5-21-15-34.2 10-11.4 18.7-23.7 26.4-36.8 7.6 13 16.5 25.4 26.4 36.8 3.5 13.3-10.1 21.4-15 34.2-1.8 4.8-1.8 9.6.4 13.7-3.9-.6-7.8-.9-11.8-.9zm0-58.1c-9-.1-16.5 7.4-16.5 16.3a16.5 16.5 0 0 0 33 0c.1-8.9-7.5-16.3-16.5-16.3z"/>
<path d="M907 237.3a73.2 73.2 0 0 0-9-7.7 16 16 0 0 0 9.9-9.4c5.6-12.5 1.8-27.9 13.6-34.8 15.1-1 30-3.6 44.7-7.4a249.8 249.8 0 0 0-7.4 44.7c-6.9 11.9-22.3 8-34.8 13.6a16 16 0 0 0-9.4 9.9 71.5 71.5 0 0 0-7.6-8.9zm41.1-41.1a16.5 16.5 0 0 0-23.3-.1 16.6 16.6 0 0 0 .1 23.3 16.6 16.6 0 0 0 23.3.1c6.4-6.4 6.3-17-.1-23.3z"/>
<path d="M237.4 237.3a73.2 73.2 0 0 0-7.7 9 16 16 0 0 0-9.4-9.9c-12.5-5.6-27.9-1.8-34.8-13.6-1-15.1-3.6-30-7.4-44.7 14.7 3.8 29.6 6.3 44.7 7.4 11.9 6.9 8 22.3 13.6 34.8a16 16 0 0 0 9.9 9.4 64.9 64.9 0 0 0-8.9 7.6zm-41.1-41.1a16.5 16.5 0 0 0-.1 23.3 16.6 16.6 0 0 0 23.3-.1c6.3-6.3 6.5-16.9.1-23.3-6.4-6.4-17-6.2-23.3.1z"/>
<path d="M568.6 563.4c2.3-.8 4.9-.8 7.2 0 2.2 1.2 4 3 5.2 5.2.8 2.3.8 4.9 0 7.2a13 13 0 0 1-5.2 5.2c-2.3.8-4.9.8-7.2 0a13 13 0 0 1-5.2-5.2c-.8-2.3-.8-4.9 0-7.2 1.2-2.2 3-4 5.2-5.2z"/>
<path d="M530.1 67.8c.9 12.3 19 26.8 11.4 36.8a72 72 0 0 0-10.7 5.5c1-3.5 1.3-7.2.6-11.1-1-5.8-10.6-17.8-8.1-24.8l6.8-6.4z"/>
<path d="M185.9 245.3c9.4 8.1 32.4 5.6 34 18a75.5 75.5 0 0 0-3.6 11.4c-1.8-3.2-4.2-6-7.4-8.3-4.8-3.3-20.1-5.1-23.3-11.9l.3-9.2z"/>
<path d="M899 185.8c-8.1 9.4-5.6 32.4-18 34a75.5 75.5 0 0 0-11.4-3.6c3.2-1.8 6-4.2 8.3-7.4 3.3-4.8 5.1-20.1 11.9-23.3l9.2.3z"/>
<path d="M614.3 67.8c-.9 12.3-19 26.8-11.4 36.8a72 72 0 0 1 10.7 5.5c-1-3.5-1.3-7.2-.6-11.1 1-5.8 10.6-17.8 8.1-24.8l-6.8-6.4z"/>
<path d="M245.4 185.8c8.1 9.4 5.6 32.4 18 34 3.7-1.5 7.5-2.8 11.4-3.6-3.2-1.8-6-4.2-8.3-7.4-3.3-4.8-5.1-20.1-11.9-23.3l-9.2.3z"/>
<path d="M958.5 245.3c-9.4 8.1-32.4 5.6-34 18 1.5 3.7 2.8 7.5 3.6 11.4 1.8-3.2 4.2-6 7.4-8.3 4.8-3.3 20.1-5.1 23.3-11.9l-.3-9.2z"/>
<path d="M508.1 88.7c-.1 10.9 12.7 21.7 9.2 32.6-1.5 1.6-3 3.3-4.4 5.2-.4-1.9-1.2-3.8-2.3-5.4-1.5-2.4-3.7-4-5.7-6-5-4.7-7.8-10.1-6.1-16.3a104 104 0 0 1 7.1-7.8l2.2-2.3z"/>
<path d="M185.1 275.6c7.6 7.7 24.3 6.4 29.6 16.6.1 2.2.3 4.5.6 6.7-1.6-1-3.4-1.8-5.5-2.2-2.8-.6-5.5-.2-8.3-.2-6.9.2-12.7-1.7-15.8-7.2l-.6-10.5v-3.2z"/>
<path d="M868.7 185c-7.7 7.6-6.4 24.3-16.6 29.6-2.2.1-4.5.3-6.7.6 1-1.6 1.8-3.4 2.2-5.5.6-2.8.2-5.5.2-8.3-.2-6.9 1.7-12.7 7.2-15.8 4.6-.4 9.1-.6 13.7-.6z"/>
<path d="M636.3 88.7c.1 10.9-12.7 21.7-9.2 32.6 1.5 1.6 3 3.3 4.4 5.2.4-1.9 1.2-3.8 2.3-5.4 1.5-2.4 3.7-4 5.7-6 5-4.7 7.8-10.1 6.1-16.3-2.2-2.7-4.6-5.3-7-7.8l-2.3-2.3z"/>
<path d="M275.7 185c7.7 7.6 6.4 24.3 16.6 29.6 2.2.1 4.5.3 6.7.6-1-1.6-1.8-3.4-2.2-5.5-.6-2.8-.2-5.5-.2-8.3.2-6.9-1.7-12.7-7.2-15.8l-10.5-.6h-3.2z"/>
<path d="M959.3 275.6c-7.6 7.7-24.3 6.4-29.6 16.6-.1 2.2-.3 4.5-.6 6.7 1.6-1 3.4-1.8 5.5-2.2 2.8-.6 5.5-.2 8.3-.2 6.9.2 12.7-1.7 15.8-7.2l.6-10.5v-3.2z"/>
<path d="M505.1 173.5c.7 3.4 1.6 6.8 2.6 10.2-7.1-3.6-16.5-3.4-22.2-8.4a66.6 66.6 0 0 1-4.2-13.8c.9.8 1.8 1.5 2.8 2.1 3.4 1.9 7.2 2.1 10.8 3.2 4.4 1.3 7.7 3.5 10.2 6.7z"/>
<path d="M242.9 337.7c2.9 1.9 6 3.7 9.1 5.4-7.5 2.5-14.1 9.3-21.7 9.7a77.5 77.5 0 0 1-12.7-6.8c1.2 0 2.4-.2 3.5-.5 3.8-1 6.6-3.6 9.9-5.4 3.9-2.1 7.9-2.9 11.9-2.4z"/>
<path d="M806.6 242.8c-1.9 2.9-3.7 6-5.4 9.1-2.5-7.5-9.3-14.1-9.7-21.7 1.9-4.4 4.1-8.7 6.8-12.7 0 1.2.2 2.3.5 3.5 1 3.8 3.6 6.6 5.4 9.9 2.1 3.9 2.9 7.9 2.4 11.9z"/>
<path d="M639.3 173.5c-.7 3.4-1.6 6.8-2.6 10.2 7.1-3.6 16.5-3.4 22.2-8.4 1.9-4.6 3.3-9.2 4.2-13.8-.8.8-1.8 1.5-2.8 2.1-3.4 1.9-7.2 2.1-10.8 3.2-4 1.1-7.6 3.4-10.2 6.7z"/>
<path d="M337.8 242.8c1.9 2.9 3.7 6 5.4 9.1 2.5-7.5 9.3-14.1 9.7-21.7a77.5 77.5 0 0 0-6.8-12.7c0 1.2-.2 2.4-.5 3.5-1 3.8-3.6 6.6-5.4 9.9-2 3.6-2.9 7.8-2.4 11.9z"/>
<path d="M901.5 337.7c-2.9 1.9-6 3.7-9.1 5.4 7.5 2.5 14.1 9.3 21.7 9.7 4.4-1.9 8.7-4.1 12.7-6.8-1.2 0-2.4-.2-3.5-.5-3.8-1-6.6-3.6-9.9-5.4-3.6-2-7.8-2.9-11.9-2.4z"/>
<path d="M504.5 144c-.9 3.5-1.3 7.1-1.4 10.7-1.8-1.8-4-3.3-6.2-4.7-7-4.2-17.6-8.9-14.9-19v-.2c.5-2.4 1.2-4.8 2-7.2l1.9-4.6c.2 2.7.9 5.4 2.3 8 3.2 6.5 12.1 10.9 16.3 17z"/>
<path d="M221.6 317.3c1.9 3.1 4.1 5.9 6.5 8.5-2.6.1-5.2.4-7.7 1.1-7.9 2-18.8 6.2-24-2.9l-.1-.1-3.7-6.5-1.9-4.5c2 1.7 4.5 3.1 7.3 4 7 2.2 16.3-.9 23.6.4z"/>
<path d="M827 221.5c-3 1.9-5.9 4.1-8.5 6.5-.1-2.6-.4-5.2-1.1-7.7-2-7.9-6.2-18.8 2.9-24l.1-.1 6.5-3.7 4.5-1.9c-1.7 2-3.1 4.5-4 7.3-2.2 7 1 16.3-.4 23.6z"/>
<path d="M639.9 144a55 55 0 0 1 1.4 10.7c1.8-1.8 4-3.3 6.2-4.7 7-4.2 17.6-8.9 14.9-19v-.2c-.6-2.4-1.2-4.8-2-7.2l-1.9-4.6c-.2 2.7-1 5.4-2.3 8-3.2 6.5-12 10.9-16.3 17z"/>
<path d="M317.4 221.5c3.1 1.9 5.9 4.1 8.5 6.5 0-2.6.5-5.1 1.1-7.7 2-7.9 6.2-18.8-2.9-24l-.1-.1-6.5-3.7-4.5-1.9c1.7 2 3.1 4.5 4 7.3 2.2 7-.9 16.3.4 23.6z"/>
<path d="M922.8 317.3c-1.9 3-4.1 5.9-6.5 8.5 2.6 0 5.1.5 7.7 1.1 7.9 2 18.8 6.2 24-2.9l.1-.1 3.7-6.5 1.9-4.5c-2 1.7-4.5 3.1-7.3 4-6.9 2.2-16.3-.9-23.6.4z"/>
</g>
</g>
<g class="myIdInfo" id="myIdInfo" text-anchor="middle"/>
</svg>

帮手:

  • getPosOnCircle() – 获取弧坐标(基于 Paul LeBeau 的回答 – 纯 svg 饼图,文本居中对齐 ) – 也有助于饼图标注
  • checkPointIntersection() – 用于单数 x/y 交集检查
  • checkCircleIntersects() – 检查多个角度和半径的点。 Returns 交点数组
  • getBestMatchArr() – 通过比较交集数组的长度找到最佳匹配

svg 优化:

  • 减少不必要的背景元素
  • 按父组继承属性

您可以通过从背景图层中删除 inner/counter 形状来显着减少 svg 数据(~ 从 400KB 到 150KB(包括标签)——仍然不轻)。

然而,正如@the Hutt 已经指出的那样:
您应该静态缓存或存储检索到的标签坐标,以避免每次加载 site/view.

时进行昂贵的 isPointInFill() 计算

codepen example