1

I'm trying to label a pie chart of sorts. I can't seem to get the text to follow the outer edge, inside the segment. All my attempts to modify alignment result in different variations of the text being jammed into the acute angle. The transforms on the text element should position the text with the related path.

What needs to happen here? I imagine I could create additional transparent paths just for this, but that seems unnecessary.

There's a similar question but I can't figure out how it's different from my scenario or what actually makes it work. The accepted answer is quite vague.

Some things I've looked into:

<svg class="ring-svg" width="100%" height="100%" viewBox="0 0 1000 1000">
  <g class="sector-g" transform="translate(500,500)">
    <path id="sectorPath_0" d="M 0 0 433.01270189221935 -249.99999999999997 A 500 500 0 0 0 3.061616997868383e-14 -500Z" fill="#e8ec77"></path>
     
    <text class="segment-label" x="500" y="0" style="transform: rotate(-60deg) translateX(-1.5rem) translateZ(0px);">
       <textPath xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#sectorPath_0" text-anchor="middle">Label Text</textPath>
    </text>
  </g>
</svg>
isherwood
  • 58,414
  • 16
  • 114
  • 157

2 Answers2

1

If you want the text on the outside, you have to draw the arc in the opposite direction. Use startOffset in your textPath to position the label correctly - it's easier than trying to calculate the stacked transforms.

Although CSS units are supported - they're not supported consistently, so I would advise not to use things like rems, ems, pts, etc. inside an inline SVG.

<svg class="ring-svg" width="100%" height="100%" viewBox="0 0 1000 1000">
  <g class="sector-g" transform="translate(500,500)">
    <path id="sectorPath_0" d="M 0 0 L 0 -500 A 500 500 0 0 1 433 -250 Z" fill="#e8ec77"></path>
     
    <text class="segment-label" x="500" y="0" >
       <textPath xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#sectorPath_0" text-anchor="middle" startOffset="17%">Label Text</textPath>
    </text>
  </g>
</svg>

Update:

Sorry - didn't twig that you wanted the label to be internal. In that case, just add a small transform to push the label inside.

<svg class="ring-svg" width="100%" height="100%" viewBox="0 0 1000 1000">
  <g class="sector-g" transform="translate(500,500)">
    <path id="sectorPath_0" d="M 0 0 L 0 -500 A 500 500 0 0 1 433 -250 Z" fill="#e8ec77"></path>
     <g transform="translate(-15,20)">
    <text class="segment-label" x="500" y="0" >
       <textPath xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#sectorPath_0" text-anchor="middle" startOffset="17%">Label Text</textPath>
    </text>
    </g>
  </g>
</svg>
Michael Mullany
  • 30,283
  • 6
  • 81
  • 105
1

As pointed out by Michael Mullany
You need to change your textPath drawing direction to get the desired alignment.

Ideally, you could use the textPath related side attribute

(2023: only supported by Firefox!)

<svg class="ring-svg" width="100%" height="100%" viewBox="0 0 1000 1000">
  <g class="sector-g" transform="translate(500,500)">
    <path id="sectorPath_0" d="M 0 0 433.01270189221935 -249.99999999999997 A 500 500 0 0 0 3.061616997868383e-14 -500Z" fill="#e8ec77"></path>
    <text class="segment-label" x="0" y="0" dy="20">
       <textPath href="#sectorPath_0" startOffset="50%" text-anchor="middle" side="right">Label Text</textPath>
    </text>
  </g>
</svg>

<svg class="ring-svg" width="100%" height="100%" viewBox="0 0 1000 1000">
  <g class="sector-g" transform="translate(500,500)">
    <path id="sectorPath_1" d="M 0 0 433.01270189221935 -249.99999999999997 A 500 500 0 0 0 3.061616997868383e-14 -500Z" fill="#e8ec77"></path>
    <text class="segment-label" x="0" y="0" dy="20">
       <textPath href="#sectorPath_1" startOffset="50%" text-anchor="middle" side="left">Label Text</textPath>
    </text>
  </g>
</svg>

svg textpath side attribute firefox vs. chromium

Left: Firefox (using side:right); Right: Chromium (omitting side)

That's a bummer. However, the above example already simplifies the textPath syntax significantly:

<text class="segment-label" x="0" y="0" dy="20">
   <textPath href="#sectorPath_0" startOffset="50%" text-anchor="middle" side="right">Label Text</textPath>
</text>

The <text> x attribute will add an additional startOffset
We just apply x="0" as well as startOffset="50%" and text-anchor="middle"

The dy attribute is used to apply a baseline-offset.

Reversing the <textPath>

Fortunately, we can (at least in your case) assume, all pie chart segments are generated based on the same pattern/structure:

  1. starting at the center of the pie chart full circle
  2. drawing the pie chart "wedge" counter clockwise (for whatever reason ...)
  3. all commands are absolute

So we can rewrite/reverse the initial segment pathData in a predictable way.

I'm using the getPathData() polyfill to parse the required command data.

let pieWedges = document.querySelectorAll(".segment");

pieWedges.forEach((path) => {
  let pathData = path.getPathData();
  let reversedPathData = reversePieWedge(pathData);
  path.setPathData(reversedPathData);
});


function reversePieWedge(pathData) {
  let reversedPathData = [
    // M - unchanged
    pathData[0],
    { type: "L", values: [pathData[2].values[5], pathData[2].values[6]] },

    // reverse arcto
    {
      type: "A",
      values: [
        pathData[2].values[0],
        pathData[2].values[1],
        pathData[2].values[2],
        pathData[2].values[3],
        // reverse sweep flag
        1,
        // inherit final point from first lineto
        pathData[1].values[0],
        pathData[1].values[1]
      ]
    },
    { type: "Z", values: [] }
  ];

  return reversedPathData;
}
<svg id="svg" class="ring-svg" width="100%" height="100%" viewBox="0 0 1000 1000">
  <path d="M500 500L938.15 259.12A500 500 0 0 0 500 0L500 500Z" fill="#80e080" class="segment segment-17_000 segment-1 segCustom" id="seg0" />
  <path d="M500 500L593.69 991.14A500 500 0 0 0 938.15 259.12L500 500z" fill="#ccc" class="segment segment-30_000 segment-2 segCustom" id="seg1" />
  
    <!-- labels -->
    <text class="segment-label" x="10" y="0"  dy="25" >
       <textPath href="#seg0" startOffset="50%" text-anchor="middle">Label Text</textPath>
    </text>
    <text class="segment-label" x="10" y="0"  dy="25" >
       <textPath href="#seg1" startOffset="50%" text-anchor="middle">Label Text</textPath>
    </text>
  </svg>


<script src="https://cdn.jsdelivr.net/npm/path-data-polyfill@1.0.4/path-data-polyfill.min.js"></script>

As you can see, regardless of the segments's arc length, the label is always centered.

Check/fix the segment path generating script?

As mentioned before, it is better to draw/generate pie charts in a clockwise direction:

renderPoint(svg, [500, 500], "green", "2%", "1");
renderPoint(svg, [500, 0], "orange", "2%", '2');
renderPoint(svg, [933, 250], "red", "2%", '3');

function renderPoint(
  svg,
  coords,
  fill = "red",
  r = "2",
  textVal = "",
  opacity = "1",
  id = "",
  className = ""
) {
  //console.log(coords);
  if (Array.isArray(coords)) {
    coords = {
      x: coords[0],
      y: coords[1]
    };
  }

  let marker = `<circle class="${className}" opacity="${opacity}" id="${id}" cx="${coords.x}" cy="${coords.y}" r="${r}" fill="${fill}">
  <title>${coords.x} ${coords.y}</title></circle>
  <text 
  x="${coords.x}" 
  y="${coords.y}" 
  font-size="${parseFloat(r)*15}" 
  dominant-baseline="middle" 
  text-anchor="middle">${textVal}</text>`;
  svg.insertAdjacentHTML("beforeend", marker);
}
svg{
  width:30em;
  overflow: visible;
}
<svg id="svg" class="ring-svg" width="100%" height="100%" viewBox="0 0 1000 1000">
  <g class="sector-g" transform="translate(500,500)">
    <path id="sectorPath_0" d="
M 0 0
L 0 -500
A 500 500 0 0 1 433 -250
Z
" fill="#e8ec77"></path>
    <text class="segment-label" x="0" y="0" dy="20" >
       <textPath xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#sectorPath_0" startOffset="50%" text-anchor="middle">Label Text</textPath>
    </text>
  </g>
</svg>

So you should check if your pie-chart generator includes an option/parameter to change drawing directions.

herrstrietzel
  • 11,541
  • 2
  • 12
  • 34