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
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)