javascriptsvgd3.jscentroid

Path centroid calculation


I'm trying to display an icon in the center of each path element but it doesn't look right

enter image description here

My code simply calculated the center point based on the width & height of the path

const center = {
  x: (bbox.x - svg_box.x) + bbox.width / 2,
  y: (bbox.y - svg_box.y) + bbox.height / 2,
}

JSFiddle

Can this be improved using a centroid function? Or using d3?
I could not figure out how to find the centroid of an existing path using d3.

Thank you


Solution

  • D3 has two centroid methods: arc.centroid and path.centroid (from d3-geo), and none will work with path elements like you have here.

    However, we can use path.centroid for getting the centroids of those paths, but it's quite hacky: you have to create a geoJSON object based on your actual path just to pass that object to path.centroid. Therefore, you'd be better creating your own.

    That said, let's see how that approach works. We can iterate over each path, getting its length and setting a dummy geoJSON object:

    const pathLength = n[i].getTotalLength();
    let index = 0;
    const geoJSONObject = {
        "type": "Polygon",
        "coordinates": [
          []
        ]
    };
    

    Then, we move along the path and populate the geoJSON object (here 400/1237 is just a quick way to calculate the viewport values, you can use a proper matrix if you want)...

    while (index < pathLength) {
        const point = n[i].getPointAtLength(index);
        geoJSONObject.coordinates[0].push([point.x * (400 / 1237), point.y * (400 / 1232)]);
        index += precision;
    };
    

    ...and finally we pass that object to path.centroid:

    const centroid = path.centroid(geoJSONObject);
    

    Here's the snippet with that solution:

    const controls = d3.select(".controls"),
      path = d3.geoPath()
      .projection(d3.geoIdentity()),
      precision = 100;
    
    d3.selectAll("path").each((_, i, n) => {
      const pathLength = n[i].getTotalLength();
      let index = 0;
      const geoJSONObject = {
        "type": "Polygon",
        "coordinates": [
          []
        ]
      };
      while (index < pathLength) {
        const point = n[i].getPointAtLength(index);
        geoJSONObject.coordinates[0].push([point.x * (400 / 1237), point.y * (400 / 1232)]);
        index += precision;
      };
      const centroid = path.centroid(geoJSONObject);
      controls.append("div")
        .style("left", centroid[0] + "px")
        .style("top", centroid[1] + "px");
    })
    .container {
      position: relative;
      display: inline-flex;
    }
    
    path {
      outline: 1px solid #0F0;
    }
    
    .controls {
      position: absolute;
      top: 0;
      left: 0;
      width: 100%;
      height: 100%;
    }
    
    .controls>div {
      position: absolute;
      width: 5px;
      height: 5px;
      background-color: red;
    }
    <script src="https://d3js.org/d3.v7.min.js"></script>
    
    <div class="container">
    
      <div class="controls"></div>
    
      <?xml version="1.0" encoding="utf-8"?>
      <!-- Generator: Adobe Illustrator 25.4.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
      <svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="400" x="0px" y="0px" viewBox="0 0 1237 1232" style="enable-background:new 0 0 1237 1232;" xml:space="preserve">
            <style type="text/css">
              .st1 {
                fill: none;
                stroke: #000000;
                stroke-miterlimit: 10;
              }
    
            </style>
            <g>
              <path class="st1" d="M1036.3,1040.8C893.1,896.6,750.5,753,607.8,609.4c0.1-0.4,0.3-0.8,0.4-1.2c3.4,0.4,6.9,0.8,10.3,1.4
            c37.9,6.1,75.9,12.3,113.8,18.5c40.4,6.5,80.8,13.1,121.2,19.6c40.2,6.5,80.5,13,120.7,19.5c39.9,6.5,79.8,12.9,119.7,19.4
            c36.5,5.9,72.9,11.8,109.4,17.8c1.8,0.3,3.9,1.3,4.8,2.6c0.5,0.7-1,3.2-2.1,4.5c-35.1,42-67.4,86.1-92.8,134.8
            c-20.4,39.2-36.2,80.4-51.4,121.9c-8.4,23-16.1,46.2-24.1,69.3C1037.4,1038.2,1037,1039.1,1036.3,1040.8z" />
              <path class="st1" d="M604.1,609.4c0.9,5.3,1.9,10.6,2.7,15.9c3.6,23.1,7.2,46.2,10.7,69.3c3.1,20.5,6.2,41,9.4,61.4
            c3.5,22.4,7.1,44.9,10.5,67.3c3.2,20.5,6.3,41,9.4,61.4c3.2,20.5,6.4,40.9,9.6,61.4c2.8,18.2,5.6,36.4,8.4,54.6
            c3.5,22.6,7,45.2,10.5,67.8c3.2,20.5,6.3,40.9,9.5,61.4c3.1,20.3,6.3,40.6,9.4,60.9c0.7,4.7,1.8,9.5,2.3,14.2
            c0.2,1.8,0.2,4.5-0.9,5.5c-0.9,0.8-3.7,0.2-5.3-0.4c-43.3-17.2-87-33.3-131.7-46.7c-31.9-9.5-64.4-14.5-97.8-15.8
            c-45-1.8-89.9,0.3-135.9,2.9c92.9-180.7,185.4-360.9,278-541.1C603.4,609.4,603.8,609.4,604.1,609.4z" />
              <path class="st1" d="M511.8,1.5c31.1,200.5,62,400.4,93,600.2c-0.3,0.2-0.6,0.4-0.9,0.6c-2.2-2.1-4.5-4.2-6.7-6.3
            c-26.4-26.6-52.8-53.3-79.2-79.9c-22.7-22.8-45.4-45.6-68.1-68.4c-49.7-50-99.3-100-149-150c-36.8-37-73.5-74-110.3-111
            c-3.8-3.8-7.5-7.5-11.1-11.5c-1.3-1.5-2.1-3.5-3.1-5.2c1.7-0.8,3.4-1.9,5.2-2.3c21.6-4.8,43.3-9,64.7-14.4
            c44.9-11.3,89.2-24.6,130.9-44.9c39-19,72.8-45.5,103.9-75.5C491.4,22.9,501.1,12.4,511.8,1.5z" />
              <path class="st1" d="M600.9,606.3c-10.5,5.3-21,10.6-31.5,16c-69.1,34.9-138.3,69.8-207.4,104.7c-62.6,31.6-125.2,63.2-187.7,94.9
            c-36.4,18.4-72.8,36.9-109.2,55.3c-0.9,0.5-1.7,1.1-2.7,1.3c-1.4,0.4-2.8,0.5-4.2,0.7c-0.2-1.6-0.9-3.2-0.6-4.6
            c1.6-9.2,3.8-18.2,4.9-27.5c2.3-20.2,4.6-40.3,5.7-60.6c1.8-30.3,0.1-60.6-4-90.7c-5.7-41.4-15.8-81.6-31.6-120.3
            C23.7,554,13,533.3,3.2,512.2c-0.5-1-1-2-2.1-4.3c200.5,32.5,400.1,64.8,599.6,97C600.7,605.4,600.8,605.9,600.9,606.3z" />
              <path class="st1" d="M1150.2,329.6c-180,90.5-359.5,180.8-540.4,271.8c1.2-2.8,1.6-4.3,2.3-5.7c30.1-58.7,60.3-117.3,90.4-176
            c22.5-43.9,45-87.7,67.5-131.6c37.8-73.6,75.6-147.3,113.5-220.8c0.8-1.6,2.5-2.7,3.8-4.1c1.2,1.5,2.8,2.7,3.7,4.4
            c24.3,46.6,51.6,91.3,84.4,132.6c33.1,41.6,74.4,73.3,119.8,99.9c16.9,9.9,34.6,18.6,51.9,27.8
            C1148.1,328.4,1148.9,328.9,1150.2,329.6z" />
            </g>
          </svg>
    </div>