Reputation: 76
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" 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>
I don't want to use jquery if I can avoid it
Upvotes: 0
Views: 673
Reputation: 17265
You face several problems:
<text>
elements – js to the rescue!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.
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>
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
getTotalLength()
(OK, we could have uses pathLength property as well ...)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).path.isPointInFill(point)
and save them to an array of DOMPoints containing x/y coordinates (let intersects)let midIndex = Math.ceil((intersects.length - 1) / 2)
)<text>
elements to the pie chart svg with right x/y coordinates (the label text is retrieved from a data-attribute)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("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>
The only js functions left are responsible for event binding (click, mouseover etc.) and toggling.
you should also check out the way more elegant approach by Paul LeBeau (Pure svg pie chart, text align center)
Upvotes: 2