Home > Net >  SVG pie chart Text labels not showing using just css html and svg (no chart js or other)
SVG pie chart Text labels not showing using just css html and svg (no chart js or other)

Time:02-23

I can see "Project" my text in the editor window but cannot get it to show on the graph or in the pie wedge area. Any help would be greatly appreciated. I need the label text aligned to the right or to show on top. I stacked other images on top of the graph so I tried a z-index to do it. Here is my code.

/* SHOW LABEL ON HOVER */
jQuery(".group_path").hover(
  function() {
    jQuery(this).find(".text_toggle").css("display", "block");
  },
  function() {
    jQuery(this).find(".text_toggle").css("display", "none");
  }
);
/* Trying to get text to show as labels - also Jquery code in script file */

.text_toggle {
  display: none;
  fill: transparent;
}

.group_path:hover .text_toggle {
  display: block;
  font-size: 1em;
  text-align: right;
  z-index: 5;
}
<!-- Jquery 3.6 -->
<script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj 3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>


<!-- Body -->
<svg viewBox='-1 -1 2 2' style='transform: scale(1.0);  rotate(-90deg)'>
  <g id="4" >
    <g id="4.01"  fill='rgb(84,161,229)'  >
      <path stroke='white' stroke-width='.0125px' d='M 1.000000 0.000000 A 1 1 0 0 1 0.873262 0.487250 L 0 0 '></path>
      <text  d='M 1.000000 0.000000 A 1 1 0 0 1 0.873262 0.487250 L 0 0 '  ><tspan >Project</tspan></text>
    </g>
    <g id="4.02"  fill='rgb(242,162,84)'>
      <path  stroke='white' stroke-width='.0125px' d='M 0.873262 0.487250 A 1 1 0 0 1 -0.147119 0.989119 L 0 0 '></path>
    </g>
    <g id="4.03"  fill='rgb(237,110,133)' >
      <path stroke='white' stroke-width='.0125px' d='M -0.147119 0.989119 A 1 1 0 0 1 -0.689114 0.724653 L 0 0 '></path>
    </g>
    <g id="4.04"  fill='rgb(173,205,225)' >
      <path stroke='white' stroke-width='.0125px' d='M -0.689114 0.724653 A 1 1 0 0 1 -0.915241 0.402907 L 0 0 '></path>
    </g>
    <g id="4.05"  fill='rgb(187,221,147)' >
      <path stroke='white' stroke-width='.0125px' d='M -0.915241 0.402907 A 1 1 0 0 1 -0.946085 0.323917 L 0 0 '></path>
    </g>
    <g id="4.06"  fill='rgb(238,158,155)' >
      <path stroke='white' stroke-width='.0125px' d='M -0.946085 0.323917 A 1 1 0 0 1 -0.978581 -0.205863 L 0 0 '></path>
    </g>
    <g id="4.07"  fill='rgb(84,161,229)' >
      <path stroke='white' stroke-width='.0125px' d='M -0.978581 -0.205863 A 1 1 0 0 1 -0.879316 -0.476238 L 0 0 '></path>
    </g>
    <g id="4.08"  fill='rgb(108,190,191)'>
      <path stroke='white' stroke-width='.0125px' d='M -0.879316 -0.476238 A 1 1 0 0 1 -0.527846 -0.849340 L 0 0 '></path>
    </g>
    <g id="4.09"  fill='rgb(242,162,84)' >
      <path stroke='white' stroke-width='.0125px' d='M -0.527846 -0.849340 A 1 1 0 0 1 0.056518 -0.998402 L 0 0 '></path>
    </g>
    <g id="4.10"  fill='rgb(237,110,133)'>
      <path stroke='white' stroke-width='.0125px' d='M 0.056518 -0.998402 A 1 1 0 0 1 0.543760 -0.839241 L 0 0 '></path>
    </g>
    <g id="4.11"  fill='rgb(173,205,225)'>
      <path stroke='white' stroke-width='.0125px' d='M 0.543760 -0.839241 A 1 1 0 0 1 0.711535 -0.702650 L 0 0 '></path>
    </g>
    <g id="4.12"  fill='rgb(187,221,147)'>
      <path stroke='white' stroke-width='.0125px' d='M 0.711535 -0.702650 A 1 1 0 0 1 0.724653 -0.689114 L 0 0 '></path>
    </g>
    <g id="4.13"  fill='rgb(42,228,229)'>
      <path stroke='white' stroke-width='.00625px' d='M 0.724653 -0.689114 A 1 1 0 0 1 1.000000 -0.000000 L 0 0 '></path>
    </g>                      
    <circle fill='#fff' cx='0' cy='0' r='0.80'/>
    </g>
</svg>

I don't want to use jquery if I can avoid it

CodePudding user response:

You face several problems:

  • text elements can't use any d attributes (preserved for path elements)
  • css property z-index won't have any effect on svg elements – you'll need to add labels on top of your pie chart segments
  • you need to get x/y coordinates for your label <text> elements – js to the rescue!
  • ids are not ideal (starting with numbers, containing periods) – those elements are not selectable in css or js (unless you escape them)
  • (better close your segments' paths via z command)

How to get the right text anchor coordinates

pie chart test anchors

For properly aligned text labels, we need to get the x/y coordinates of the semi-arc of each segment.
(illustrated by red circles)

The main concept is to check where pie chart wedges are intersecting a "centerline" circle: we need to add this auxiliary circle element with a radius between outer radius=1 and inner radius=0.8 – so our centerline circle needs to have a radius of 0.9.

Example 1: pre-processing (find x/y for text labels)

let pie = document.querySelector("svg");
let segments = pie.querySelectorAll(".group_path");
let labelGroupHtml = "";
let textanchors = "";

// auxiliary circle element to get label coordinates
let circleIntersect = document.querySelector(".circleIntersect");
let circleLength = circleIntersect.getTotalLength();
// define precision for intersection checking
let steps = circleLength / 180;
let circlePoints = [];
for (let i = 0; i < circleLength; i  = steps) {
  let point = circleIntersect.getPointAtLength(i);
  circlePoints.push(point);
}

// find intersections beween each piechart slices and auxiliary circle
function getIntersect(path) {
  let intersects = [];
  let middlePoint = 0;
  for (let i = 0; i < circlePoints.length; i  ) {
    let point = circlePoints[i];
    let isIntersect = path.isPointInFill(point);
    if (isIntersect) {
      intersects.push({
        x:  point.x.toFixed(2),
        y:  point.y.toFixed(2)
      });
    }
  }
  if (intersects.length) {
    // get segment's middle coordinates
    let midIndex = Math.ceil((intersects.length - 1) / 2);
    middlePoint = intersects[midIndex];
  }
  return middlePoint;
}

segments.forEach(function(el, i) {
  let segementId = "label_"   i;
  let path = el.querySelector("path");
  let labelText = path.getAttribute("data-label");
  // add generic labels if not defined
  labelText = labelText ? labelText : "Segment"   (i   1);
  path.setAttribute("data-target", segementId);

  let intersect = getIntersect(path);
  if (intersect) {
    let midX = intersect["x"];
    let midY = intersect["y"];
    textanchors  =
      '<circle  fill="red" cx="'  
      midX  
      '" '  
      'cy="'  
      midY  
      '" r="0.02" />';

    let label =
      '<text dy="2%" id="'  
      segementId  
      '"  x="'  
      midX  
      '" y="'  
      midY  
      '" transform="rotate(90 '  
      midX  
      " "  
      midY  
      ')"   ><tspan >'  
      labelText  
      "</tspan></text>";
    labelGroupHtml  = label;
  }
});

pie.insertAdjacentHTML(
  "beforeend",
  '<g >'   labelGroupHtml   "</g>"
);

// just for illustrating the retrieved text anchors
pie
  .querySelector(".preprocessing")
  .insertAdjacentHTML(
    "beforeend",
    '<g >'   textanchors   "</g>"
  );

// event listeners
let pieSegemts = pie.querySelectorAll("path");
if (pieSegemts.length) {
  pieSegemts.forEach(function(segment, i) {
    segment.addEventListener("click", function(e) {
      /**
       * uncomment the closelabels call and
       * mouseleave event listener if you need only one segemnt to be active
       */
      /*
      closeLabels();
      */
      let labelSelector = e.currentTarget.getAttribute("data-target");
      let label = pie.querySelector("#"   labelSelector);
      label.classList.toggle("label_active");
      segment.classList.toggle("segment_active");
    });
    /*
    segment.addEventListener("mouseleave", function (e) {
      closeLabels();
    });
    */
  });
}

// hide other labels
function closeLabels() {
  let opened = pie.querySelectorAll(".label_active, .segment_active");
  opened.forEach(function(el, i) {
    el.classList.remove("label_active");
    el.classList.remove("segment_active");
  });
}

// ungroup elements – inherit properties
ungroup(".group_path");

function ungroup(selector) {
  let groups = document.querySelectorAll(selector);
  groups.forEach(function(group, i) {
    let attributes = [...group.attributes];
    let children = [...group.children];
    children.forEach(function(el, i) {
      attributes.forEach(function(att, i) {
        el.setAttribute(att["name"], att["nodeValue"]);
        el.classList.add("segment");
      });
      group.parentNode.insertBefore(el, group.nextElementSibling);
      group.remove();
    });
  });
}

// replace ids containing numbers
cleanNumIds();

function cleanNumIds() {
  let idEls = document.querySelectorAll("[id]");
  idEls.forEach(function(el, i) {
    let id = el.id;
    let idNum = ( id).toString();
    if (idNum === id) {
      el.setAttribute("data-id", id);
      el.id = "seg_"   id.replaceAll(".", "-");
    }
  });
}
body {
  font-family: "Sogoe UI", "Open Sans", Arial;
}

svg {
  display: inline-block;
  width: 20em;
  overflow: visible;
  border: 1px solid #ccc;
}

.segment {
  stroke: #fff;
  stroke-width: 0.0125;
}

.segment_active {
  opacity: 0.5;
}

.text_label {
  font-size: 0.1px;
  fill: #000;
  text-anchor: start;
  visibility: hidden;
}

.text_label,
.notSelectable {
  user-select: none;
  pointer-events: none;
}

.label_active {
  visibility: visible;
}
<!-- Body -->
<svg viewBox='-1 -1 2 2' style='transform:rotate(-90deg)'>
  <g id="4">
    <g id="4.01"  fill='rgb(84,161,229)'>
      <path d='M 1.000000 0.000000 A 1 1 0 0 1 0.873262 0.487250 L 0 0z' data-label="Project"></path>
    </g>
    <g id="4.02"  fill='rgb(242,162,84)'>
      <path d='M 0.873262 0.487250 A 1 1 0 0 1 -0.147119 0.989119 L 0 0z' data-label="Segment 2"></path>
    </g>
    <g id="4.03"  fill='rgb(237,110,133)'>
      <path d='M -0.147119 0.989119 A 1 1 0 0 1 -0.689114 0.724653 L 0 0z'></path>
    </g>
    <g id="4.04"  fill='rgb(173,205,225)'>
      <path d='M -0.689114 0.724653 A 1 1 0 0 1 -0.915241 0.402907 L 0 0z'></path>
    </g>
    <g id="4.05"  fill='rgb(187,221,147)'>
      <path d='M -0.915241 0.402907 A 1 1 0 0 1 -0.946085 0.323917 L 0 0z'></path>
    </g>
    <g id="4.06"  fill='rgb(238,158,155)'>
      <path d='M -0.946085 0.323917 A 1 1 0 0 1 -0.978581 -0.205863 L 0 0z'></path>
    </g>
    <g id="4.07"  fill='rgb(84,161,229)'>
      <path d='M -0.978581 -0.205863 A 1 1 0 0 1 -0.879316 -0.476238 L 0 0z'></path>
    </g>
    <g id="4.08"  fill='rgb(108,190,191)'>
      <path d='M -0.879316 -0.476238 A 1 1 0 0 1 -0.527846 -0.849340 L 0 0z'></path>
    </g>
    <g id="4.09"  fill='rgb(242,162,84)'>
      <path d='M -0.527846 -0.849340 A 1 1 0 0 1 0.056518 -0.998402 L 0 0z'></path>
    </g>
    <g id="4.10"  fill='rgb(237,110,133)'>
      <path d='M 0.056518 -0.998402 A 1 1 0 0 1 0.543760 -0.839241 L 0 0z'></path>
    </g>
    <g id="4.11"  fill='rgb(173,205,225)'>
      <path d='M 0.543760 -0.839241 A 1 1 0 0 1 0.711535 -0.702650 L 0 0z'></path>
    </g>
    <g id="4.12"  fill='rgb(187,221,147)'>
      <path d='M 0.711535 -0.702650 A 1 1 0 0 1 0.724653 -0.689114 L 0 0z'></path>
    </g>
    <g id="4.13"  fill='rgb(42,228,229)'>
      <path d='M 0.724653 -0.689114 A 1 1 0 0 1 1.000000 -0.000000 L 0 0z'></path>
    </g>
    <circle fill='#fff' cx='0' cy='0' r='0.80' />
  </g>
  <!-- pseudo donut hole -->
  <circle fill="#fff" cx='0' cy='0' r='0.80' />
  <!-- circle for text x/y analyzing -->
  <g >
    <circle  stroke-width="0.01" stroke='red' fill="none" stroke-width="0.1" cx='0' cy='0' r='0.9' />
  </g>
</svg>

How it works:
We need to "travel" along the aforementioned centerline circle and check when segments intersects:
First we need to get this circles pathlength

  • by getTotalLength() (OK, we could have uses pathLength property as well ...)
  • then we split the circumference into segments resulting in an array of path length positions by getPointAtLength() . In this example 180 divisions/segments to provide enough precision when finding the ideal text x/y for a segment's label (with 100 divisions we might not get very thin pie wedges).
  • we can then check each path (pie segment) for points intersecting with the centerline circle by path.isPointInFill(point) and save them to an array of DOMPoints containing x/y coordinates (let intersects)
  • we get multiple intersecting points – the one we're interested in is the middle point (let midIndex = Math.ceil((intersects.length - 1) / 2))
  • now we can append <text> elements to the pie chart svg with right x/y coordinates (the label text is retrieved from a data-attribute)

... quite a lot of js?

Once the pie chart's svg is optimized and transformed you can save it as a static asset (e.g. by inspecting it in dev tools) and remove the pre-processing functions like so:

let pie = document.querySelector("svg");
let segments = pie.querySelectorAll(".group_path");

// event listeners
// event listeners
let pieSegemts = pie.querySelectorAll("path");
if (pieSegemts.length) {
  pieSegemts.forEach(function (segment, i) {
    segment.addEventListener("click", function (e) {
      /**
       * uncomment the closelabels call and
       * mouseleave event listener if you need only one segemnt to be active
       */
      /*
      closeLabels();
      */
      let labelSelector = e.currentTarget.getAttribute("data-target");
      let label = pie.querySelector("#"   labelSelector);
      label.classList.toggle("label_active");
      segment.classList.toggle("segment_active");
    });
    /*
    segment.addEventListener("mouseleave", function (e) {
      closeLabels();
    });
    */
  });
}

function closeLabels() {
  let opened = pie.querySelectorAll(".label_active, .segment_active");
  opened.forEach(function (el, i) {
    el.classList.remove("segment_active");
    el.classList.remove("label_active");
  });
}
body {
  font-family: "Sogoe UI", "Open Sans", Arial;
}
svg {
  display: inline-block;
  width: 20em;
  overflow: visible;
  border: 1px solid #ccc;
}

.segment {
  stroke: #fff;
  stroke-width: 0.0125;
}

.segment_active {
  opacity: 0.5;
}

.text_label {
  font-size: 0.1px;
  fill: #000;
  text-anchor: start;
  visibility: hidden;
}
.text_label,
.notSelectable {
  user-select: none;
  pointer-events: none;
}

.label_active {
  visibility: visible;
}
<svg viewBox="-1 -1 2 2" style="transform:rotate(-90deg)">
  <g id="seg_4" data-id="4">
    <path d="M 1.000000 0.000000 A 1 1 0 0 1 0.873262 0.487250 L 0 0z" data-label="Project" data-target="label_0" id="seg_4-01"  fill="rgb(84,161,229)" data-id="4.01"></path>
    <path d="M 0.873262 0.487250 A 1 1 0 0 1 -0.147119 0.989119 L 0 0z" data-label="Segment 2" data-target="label_1" id="seg_4-02"  fill="rgb(242,162,84)" data-id="4.02"></path>
    <path d="M -0.147119 0.989119 A 1 1 0 0 1 -0.689114 0.724653 L 0 0z" data-target="label_2" id="seg_4-03"  fill="rgb(237,110,133)" data-id="4.03"></path>
    <path d="M -0.689114 0.724653 A 1 1 0 0 1 -0.915241 0.402907 L 0 0z" data-target="label_3" id="seg_4-04"  fill="rgb(173,205,225)" data-id="4.04"></path>
    <path d="M -0.915241 0.402907 A 1 1 0 0 1 -0.946085 0.323917 L 0 0z" data-target="label_4" id="seg_4-05"  fill="rgb(187,221,147)" data-id="4.05"></path>
    <path d="M -0.946085 0.323917 A 1 1 0 0 1 -0.978581 -0.205863 L 0 0z" data-target="label_5" id="seg_4-06"  fill="rgb(238,158,155)" data-id="4.06"></path>
    <path d="M -0.978581 -0.205863 A 1 1 0 0 1 -0.879316 -0.476238 L 0 0z" data-target="label_6" id="seg_4-07"  fill="rgb(84,161,229)" data-id="4.07"></path>
    <path d="M -0.879316 -0.476238 A 1 1 0 0 1 -0.527846 -0.849340 L 0 0z" data-target="label_7" id="seg_4-08"  fill="rgb(108,190,191)" data-id="4.08"></path>
    <path d="M -0.527846 -0.849340 A 1 1 0 0 1 0.056518 -0.998402 L 0 0z" data-target="label_8" id="seg_4-09"  fill="rgb(242,162,84)" data-id="4.09"></path>
    <path d="M 0.056518 -0.998402 A 1 1 0 0 1 0.543760 -0.839241 L 0 0z" data-target="label_9" id="4.10"  fill="rgb(237,110,133)"></path>
    <path d="M 0.543760 -0.839241 A 1 1 0 0 1 0.711535 -0.702650 L 0 0z" data-target="label_10" id="seg_4-11"  fill="rgb(173,205,225)" data-id="4.11"></path>
    <path d="M 0.711535 -0.702650 A 1 1 0 0 1 0.724653 -0.689114 L 0 0z" data-target="label_11" id="seg_4-12"  fill="rgb(187,221,147)" data-id="4.12"></path>
    <path d="M 0.724653 -0.689114 A 1 1 0 0 1 1.000000 -0.000000 L 0 0z" data-target="label_12" id="seg_4-13"  fill="rgb(42,228,229)" data-id="4.13"></path>
    <circle fill="#fff" cx="0" cy="0" r="0.80"></circle>
  </g>
  <!-- pseudo donut hole -->
  <circle fill="#fff" cx="0" cy="0" r="0.80"></circle>
  <g ><text dy="2%" id="label_0" x="0.87" y="0.23" transform="rotate(90 0.87 0.23)" >
      <tspan>Project</tspan>
    </text><text dy="2%" id="label_1" x="0.38" y="0.81" transform="rotate(90 0.38 0.81)" >
      <tspan>Segment 2</tspan>
    </text><text dy="2%" id="label_2" x="-0.38" y="0.81" transform="rotate(90 -0.38 0.81)" >
      <tspan>Segment3</tspan>
    </text><text dy="2%" id="label_3" x="-0.73" y="0.52" transform="rotate(90 -0.73 0.52)" >
      <tspan>Segment4</tspan>
    </text><text dy="2%" id="label_4" x="-0.84" y="0.32" transform="rotate(90 -0.84 0.32)" >
      <tspan>Segment5</tspan>
    </text><text dy="2%" id="label_5" x="-0.9" y="0.03" transform="rotate(90 -0.9 0.03)" >
      <tspan>Segment6</tspan>
    </text><text dy="2%" id="label_6" x="-0.84" y="-0.32" transform="rotate(90 -0.84 -0.32)" >
      <tspan>Segment7</tspan>
    </text><text dy="2%" id="label_7" x="-0.65" y="-0.62" transform="rotate(90 -0.65 -0.62)" >
      <tspan>Segment8</tspan>
    </text><text dy="2%" id="label_8" x="-0.2" y="-0.88" transform="rotate(90 -0.2 -0.88)" >
      <tspan>Segment9</tspan>
    </text><text dy="2%" id="label_9" x="0.26" y="-0.86" transform="rotate(90 0.26 -0.86)" >
      <tspan>Segment10</tspan>
    </text><text dy="2%" id="label_10" x="0.58" y="-0.69" transform="rotate(90 0.58 -0.69)" >
      <tspan>Segment11</tspan>
    </text><text dy="2%" id="label_11" x="0.65" y="-0.62" transform="rotate(90 0.65 -0.62)" >
      <tspan>Segment12</tspan>
    </text><text dy="2%" id="label_12" x="0.84" y="-0.32" transform="rotate(90 0.84 -0.32)" >
      <tspan>Segment13</tspan>
    </text></g>
</svg>

The only js functions left are responsible for event binding (click, mouseover etc.) and toggling.

Edit: Finding label coordinates

you should also check out the way more elegant approach by Paul LeBeau (Pure svg pie chart, text align center)

  • Related