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)
我可以在编辑器中看到“投影”我的文本 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)