SVG 饼图文本标签不显示仅使用 css html 和 svg(没有图表 js 或其他)

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

我可以在编辑器中看到“投影”我的文本 window 但无法将其显示在图表或饼图区域中。任何帮助将不胜感激。我需要标签文本向右对齐或显示在顶部。我在图表的顶部堆叠了其他图像,所以我尝试了一个 z-index 来做到这一点。这是我的代码。

/* 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" class="group_path" 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 class="text_toggle" 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" class="group_path" 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" class="group_path" 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" class="group_path" 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" class="group_path" 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" class="group_path" 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" class="group_path" 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" class="group_path" 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" class="group_path" 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" class="group_path" 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" class="group_path" 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" class="group_path" 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" class="group_path" 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>

如果可以避免,我不想使用jquery

您面临几个问题:

  • 文本元素不能使用任何 d 属性(为路径元素保留)
  • css 属性 z-index 不会对 svg 元素产生任何影响 – 你需要在上面添加标签您的饼图段
  • 您需要为标签 <text> 元素获取 x/y 坐标 – js 来救援!
  • id 不理想(以数字开头,包含句点)——这些元素在 css 或 js 中不可选择(除非你转义它们)
  • (最好通过 z 命令关闭分段路径)

如何获取正确的文本锚点坐标

为了正确对齐文本标签,我们需要获取每个段 semi-arc 的 x/y 坐标。
(以红圈表示)

主要概念是检查饼图楔形与“中心线”圆相交的位置:我们需要添加半径在外半径=1 和内半径=0.8 之间的辅助圆元素——所以我们的中心线圆需要半径为 0.9.

示例 1:pre-processing(为文本标签查找 x/y)

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 class="notSelectable" 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 +
      ')"  class="text_label" ><tspan >' +
      labelText +
      "</tspan></text>";
    labelGroupHtml += label;
  }
});

pie.insertAdjacentHTML(
  "beforeend",
  '<g class="labels">' + labelGroupHtml + "</g>"
);

// just for illustrating the retrieved text anchors
pie
  .querySelector(".preprocessing")
  .insertAdjacentHTML(
    "beforeend",
    '<g class="textanchors">' + 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" class="group_path" 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" class="group_path" 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" class="group_path" 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" class="group_path" 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" class="group_path" 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" class="group_path" 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" class="group_path" 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" class="group_path" 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" class="group_path" 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" class="group_path" 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" class="group_path" 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" class="group_path" 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" class="group_path" 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 class="preprocessing">
    <circle class="circleIntersect notSelectable" stroke-width="0.01" stroke='red' fill="none" stroke-width="0.1" cx='0' cy='0' r='0.9' />
  </g>
</svg>

工作原理:
我们需要沿着上述中心线圆“行进”并检查线段何时相交:
首先我们需要得到这个圆圈的路径长度

  • by getTotalLength()(好的,我们也可以使用 pathLength 属性 ...)
  • 然后我们将圆周分成多个段,从而得到一个路径长度位置数组 getPointAtLength() 。在此示例中,180 divisions/segments 可以在找到段标签的理想文本 x/y 时提供足够的精度(100 个分度,我们可能不会得到非常薄的饼图楔形)。
  • 然后我们可以检查每个路径(饼图段)与中心线圆相交的点 path.isPointInFill(point) 并将它们保存到包含 x/y 坐标的 DOMPoints 数组(让相交)
  • 我们得到多个相交点——我们感兴趣的是中间点 (let midIndex = Math.ceil((intersects.length - 1) / 2))
  • 现在我们可以将 <text> 元素附加到具有右 x/y 坐标的饼图 svg(标签文本是从 data-attribute 中检索的)

... 相当多的js?

饼图的 svg 优化和转换后,您可以将其保存为静态资产(例如,通过在开发工具中检查它)并删除 pre-processing 函数,如下所示:

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("mouseenter", 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" class="group_path segment" 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" class="group_path segment" 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" class="group_path segment" 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" class="group_path segment" 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" class="group_path segment" 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" class="group_path segment" 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" class="group_path segment" 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" class="group_path segment" 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" class="group_path segment" 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" class="group_path segment" 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" class="group_path segment" 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" class="group_path segment" 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" class="group_path segment" 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 class="labels"><text dy="2%" id="label_0" x="0.87" y="0.23" transform="rotate(90 0.87 0.23)" class="text_label">
      <tspan>Project</tspan>
    </text><text dy="2%" id="label_1" x="0.38" y="0.81" transform="rotate(90 0.38 0.81)" class="text_label">
      <tspan>Segment 2</tspan>
    </text><text dy="2%" id="label_2" x="-0.38" y="0.81" transform="rotate(90 -0.38 0.81)" class="text_label">
      <tspan>Segment3</tspan>
    </text><text dy="2%" id="label_3" x="-0.73" y="0.52" transform="rotate(90 -0.73 0.52)" class="text_label">
      <tspan>Segment4</tspan>
    </text><text dy="2%" id="label_4" x="-0.84" y="0.32" transform="rotate(90 -0.84 0.32)" class="text_label">
      <tspan>Segment5</tspan>
    </text><text dy="2%" id="label_5" x="-0.9" y="0.03" transform="rotate(90 -0.9 0.03)" class="text_label">
      <tspan>Segment6</tspan>
    </text><text dy="2%" id="label_6" x="-0.84" y="-0.32" transform="rotate(90 -0.84 -0.32)" class="text_label">
      <tspan>Segment7</tspan>
    </text><text dy="2%" id="label_7" x="-0.65" y="-0.62" transform="rotate(90 -0.65 -0.62)" class="text_label">
      <tspan>Segment8</tspan>
    </text><text dy="2%" id="label_8" x="-0.2" y="-0.88" transform="rotate(90 -0.2 -0.88)" class="text_label">
      <tspan>Segment9</tspan>
    </text><text dy="2%" id="label_9" x="0.26" y="-0.86" transform="rotate(90 0.26 -0.86)" class="text_label">
      <tspan>Segment10</tspan>
    </text><text dy="2%" id="label_10" x="0.58" y="-0.69" transform="rotate(90 0.58 -0.69)" class="text_label">
      <tspan>Segment11</tspan>
    </text><text dy="2%" id="label_11" x="0.65" y="-0.62" transform="rotate(90 0.65 -0.62)" class="text_label">
      <tspan>Segment12</tspan>
    </text><text dy="2%" id="label_12" x="0.84" y="-0.32" transform="rotate(90 0.84 -0.32)" class="text_label">
      <tspan>Segment13</tspan>
    </text></g>
</svg>

剩下的唯一 js 函数负责事件绑定(单击、鼠标悬停等)和切换。

编辑:查找标签坐标

你还应该看看更优雅的方法by Paul LeBeau (Pure svg pie chart, text align center)