3

I am trying to put numbers inside custom shapes. I am using getBBox() to find center of the shape. It works only on certain shapes, because center point may not be inside a hollow shape.

Demo on Codepen: https://codepen.io/arindamx01/pen/KKyLNrj?editors=1010
Here is my JavaScript code:

    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); 
    });
});

I tried using getBBox() method but it is not working for all shapes.

enter image description here

In above image, the number 4 should be inside the hollow C shape.

the Hutt
  • 16,980
  • 2
  • 14
  • 44
Arindam Sarkar
  • 224
  • 1
  • 13
  • `getBBox` is working correctly; the "4" next to path you've pointed out is indeed positioned in the center of the path's bounding box. – Josh Brobst Mar 11 '22 at 05:31
  • Yes getBBox is working correctly but is there any method to in the center of the path's bounding box. --Thank you. @JoshBrobst – Arindam Sarkar Mar 11 '22 at 06:06

2 Answers2

4

Each shape is different center of the box may not be inside the actual shape. For those hollow shapes there are no exact centers. You just need to find any suitable point that falls inside the shape.

Considering center of the box as origin we can try to find points that intersect with x and y axis. And pick a point in between two intersections:
Demo on codesandbox. It'll take some time to load.

    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;
    } 

Here is how it looks: enter image description here

Note, play with the getInsidePoint function to adjust number positions. This isn't the most efficient implementation, refer this answer for more efficient implementation approach.


Possible improvements could be:

  • Find out better points manually for each shape and put them in data-* attributes in path elements:
<path data-label-x='10' data-label-y='40' ...>...</path>

and use these co-ordinates. No need to calculate on client side every time.

  • If the diagram is scalable then mark similar shapes. And do calculations for only one shape and use the same point for all similar shapes considering rotation.
the Hutt
  • 16,980
  • 2
  • 14
  • 44
  • Your ans is good but code latancy it has proble. In low end devices it executing very solow. – Arindam Sarkar Mar 13 '22 at 18:47
  • There are 750+ shapes in the diagram! [This other solution](https://stackoverflow.com/a/40943308/15273968) has efficient way, if you are willing to do it. Otherwise you can always provide label coordinates in html yourself like I suggested in the updated answer. – the Hutt Mar 14 '22 at 04:25
3

Since your graphic (mandala) has a radial layout, you might also try to find angle based intersections.

We can assume that shapes which don't have fill intersecting centers will have some intersections around the center x/y.

Essentially, we're also using isPointInFill() to first exclude already fine coordinates within the shape's fill area and then find appropriate (x/y shifted) alternatives for all exceptions.

mandala

This approach provides some "best match" logic – i.e. prefer intersecting coordinates with more surrounding space. Illustrated above by green dots: more intersections (array.length based) will be preferred to calculate the label's x/y coordinates.

We can drastically reduce the number of "checkpoints" by analyzing only some predictable and also prioritized (angle based) points around the shapes's center (retrieved by getBBox()).

This is still quite expensive regarding performance due to the graphic's complexity.

Example check points around center:

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>

helpers:

  • getPosOnCircle() – get arc coordinates (based upon Paul LeBeau's answer – Pure svg pie chart, text align center ) – also helpful for pie chart labelling
  • checkPointIntersection() – for singular x/y intersection checking
  • checkCircleIntersects() – checking points at several angles and radii. Returns an array of intersecting points
  • getBestMatchArr() – find best match by comparing intersection arrays' length

svg optimizations:

  • reduce unnecessary background elements
  • inherit properties by parent groups

mandala bg

You can reduce the svg data significantly by removing inner/counter shapes from the background layers (~ from 400KB to 150 KB(incliuding labels) – still not lightweight).

However, as @the Hutt already pointed out:
You should rather cache or store the retrieved label coordinates statically to avoid expensive isPointInFill() calculations every time you load your site/view.

codepen example

herrstrietzel
  • 11,541
  • 2
  • 12
  • 34