Reputation: 7490
I am trying to create a pie chart using circle
element in svg
. I am able to fill values to 60%, 30% and 10% but all the circle starting from same position.
How can I make next circle start from where previous one ended?
svg { transform: rotate(-90deg); }
circle {
stroke-width: 3;
stroke-opacity: 1;
fill: none;
}
circle.stroke-yellow {
stroke: yellow;
stroke-dasharray: calc(2*3.14*50*60/100),calc(2*3.14*50);
}
circle.stroke-red {
stroke: red;
stroke-dasharray: calc(2*3.14*50*30/100),calc(2*3.14*50);
}
circle.stroke-blue {
stroke: blue;
stroke-dasharray: calc(2*3.14*50*10/100),calc(2*3.14*50);
}
<svg xmlns="http://www.w3.org/2000/svg" height="220">
<circle class="stroke-yellow" cy="110" cx="110" r="50"></circle>
<circle class="stroke-red" cy="110" cx="110" r="50"></circle>
<circle class="stroke-blue" cy="110" cx="110" r="50"></circle>
</svg>
Also stroke-width
is not working which I mentioned in CSS.
Upvotes: 2
Views: 1573
Reputation: 27245
If herrstrietzel's answer doesn't solve your issue for some reason:
Once upon a time I started working on a blog post demonstrating how to generate simple SVG donut/pie charts in React. It's incomplete but it includes all the information you'd need to compute paths for drawing each segment in your chart.
The post itself is React-centric, but the methodology doesn't require React.
The snippet below was generated using the demo in that blog post.
:root {
--color1: #6761a8;
--color2: #009ddc;
--color3: #f26430;
}
svg {
max-width: 180px;
}
path:nth-child(3n + 1) {
fill: var(--color1);
}
path:nth-child(3n + 2) {
fill: var(--color2);
}
path:nth-child(3n + 3) {
fill: var(--color3);
}
<svg viewBox="0 0 100 100">
<path d="M50.99977962889557 22.51817981476399 L50.99993333466665 0.009999666671113516 A50 50 0 1 1 21.909411013411578 91.36325434956197 L34.92449717574351 72.99954813894905 A27.5 27.5 0 1 0 50.99977962889557 22.51817981476399"></path>
<path d="M33.293128455589205 71.84331575559345 L20.27779148719977 90.20684420744341 A50 50 0 0 1 19.110270928347777 10.683023540969941 L32.65908657059322 28.656553196968876 A27.5 27.5 0 0 0 33.293128455589205 71.84331575559345"></path>
<path d="M34.25580929035654 27.45292793069627 L20.707239127704607 9.479213229769087 A50 50 0 0 1 49.000066665333264 0.009999666671113516 L49.00022037110441 22.51817981476399 A27.5 27.5 0 0 0 34.25580929035654 27.45292793069627"></path>
</svg>
Each <path>
represents one segment (slice) of the chart.
To draw the segment you need to compute the coordinates of the 4 corners and connect them with lines and arcs.
Given an angle, a radius, and a center point, you can compute an (x, y) coordinate via this formula:
function getCoordinate(angleInDegrees, radius, center = 50) {
// degrees to radians;
const radians = angleInDegrees * (Math.PI / 180);
const x = center - Math.cos(radians) * radius
const y = center - Math.sin(radians) * radius;
return [x, y];
}
So for a 90° segment with an outer radius of 50 and an inner radius of 20, you can get the corner coordinates via:
const radiusOuter = 50;
const radiusInner = 20;
const angleStart = 0;
const angleEnd = 90;
const [x1, y1] = getCoordinate(angleStart, radiusInner); // starting angle on inner radius
const [x2, y2] = getCoordinate(angleStart, radiusOuter); // starting angle on outer radius
const [x3, y3] = getCoordinate(angleEnd, radiusOuter); // ending angle on outer radius
const [x4, y4] = getCoordinate(angleEnd, radiusInner); // ending angle on inner radius
Details about each of the path commands used below can be found at MDN.
const largeArc = 0; // percent > 0.5 ? 1 : 0;
const sweepOuter = 1;
const sweepInner = 0;
const commands = [
// move to start angle coordinate, inner radius (1)
`M${x1} ${y1}`,
// line to start angle coordinate, outer radius (2)
`L${x2} ${y2}`,
// arc to end angle coordinate, outer radius (3)
`A${radiusOuter} ${radiusOuter} 0 ${largeArc} ${sweepOuter} ${x3} ${y3}`,
// line to end angle coordinate, inner radius (4)
`L${x4} ${y4}`,
// arc back to start angle coordinate, inner radius (1)
`A${radiusInner} ${radiusInner} 0 ${largeArc} ${sweepInner} ${x1} ${y1}`
];
Throw it in an SVG and add a little css and you've got your segment:
svg {
width: 250px;
border: 1px solid grey;
}
path {
fill: tomato;
}
<svg viewBox="0 0 100 100">
<path d="
M30 50
L0 50
A50 50 0 0 1 50 0
L50 30
A20 20 0 0 0 30 50
"/>
</svg>
Repeat for the other segments.
Upvotes: 5
Reputation: 17155
As @enxaneta mentioned: you will need to give each pie segment an offset by changing the dash-offset
property.
Based on your code example:
svg {
transform: rotate(-90deg);
}
circle {
stroke-width: 3;
stroke-opacity: 1;
fill: none;
}
.stroke {
stroke-width: 100;
--circumference: 314.159
}
circle.stroke-blue {
stroke: blue;
stroke-dasharray: calc( var(--circumference) * 10 / 100), var(--circumference);
stroke-dashoffset: 0;
}
circle.stroke-red {
stroke: red;
stroke-dasharray: calc( var(--circumference) * 30 / 100), var(--circumference);
stroke-dashoffset: calc( 0 - var(--circumference) * 10 / 100);
}
circle.stroke-yellow {
stroke: yellow;
stroke-dasharray: calc( var(--circumference) * 60 / 100), var(--circumference);
stroke-dashoffset: calc( 0 - var(--circumference) * 40 / 100);
}
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 220 220" height="220">
<circle class="stroke stroke-blue stroke-10" cy="110" cx="110" r="50" />
<circle class="stroke stroke-yellow stroke-60" cy="110" cx="110" r="50" />
<circle class="stroke stroke-red stroke-30" cy="110" cx="110" r="50" />
</svg>
stroke-width
needs to be '100' (radius*2);
Drawbacks:
calc()
(SVG pie-chart working only in Chrome, not in Firefox)Recommendations:
Example showing 2 slightly different svg setups:
body{
font-family: arial;
font-size:10px;
}
.icon-wrp {
position: relative;
display: inline-block;
width: 200px;
vertical-align: top;
}
.icon-wrp p{
font-size:12px;
}
<!--simple pi -->
<div class="icon-wrp">
<svg class="svgPieAsset" viewBox="0 0 63.6619772368 63.6619772368">
<symbol id="slice">
<circle transform="rotate(-90 31.8309886184 31.8309886184)" id="circle" class="percent" cx="50%" cy="50%" r="15.9154943092" fill="none" stroke-width="31.8309886184" />
</symbol>
<!--actual pi slices -->
<use class="segment" href="#slice" stroke="green" stroke-dashoffset="0" stroke-dasharray="30 100" />
<use class="segment" href="#slice" stroke="orange" stroke-dashoffset="-30" stroke-dasharray="60 100" />
<use class="segment" href="#slice" stroke="purple" stroke-dashoffset="-90" stroke-dasharray="10 100" />
</svg>
<p>1. Precice geometry based on PI. <br>Should be rendered fine on all browsers.</p>
</div>
<div class="icon-wrp">
<svg class="svgPieAsset" viewBox="0 0 100 100">
<symbol id="slice2">
<circle transform="rotate(-90 50 50)" id="circle" class="percent" cx="50%" cy="50%" r="25" fill="none" stroke-width="50%" pathLength="100" />
</symbol>
<!--actual pi slices -->
<use class="segment" href="#slice2" stroke="green" stroke-dashoffset="0" stroke-dasharray="30 100" />
<use class="segment" href="#slice2" stroke="orange" stroke-dashoffset="-30" stroke-dasharray="60 100" />
<use class="segment" href="#slice2" stroke="purple" stroke-dashoffset="-90" stroke-dasharray="10 100" />
</svg>
<p>2. Using pathLength="100". <br>Might show a tiny gap on chromium based browsers.</p>
</div>
1. left example: Is using a precice (PI based) circle geometry
The desired circumference of the circle element should be 100 svg units.
Therfore we'll need to set the ideal values like so:
radius: 15.91549430919 (100/2π)
stroke-width: 31.8309886184 (2r)
vieBox width/height: 63.6619772368 (4r)
2. right example: Is using pathLength="100"
PathLength allows us to use any circle dimensions by setting the path's length computation value to "100".
Unfortunately, you might encounter rendering imprecisions on some browsers (e.g. chromium based) resulting in visible gaps between pie segments.
Quite likely, this issue will be fixed in future versions of chromium.
Display pie segments
Eitherway, you can now easily display a percentage based pie segment/slice by setting a stroke dash length value:
Example 30% dash length; offset. 0 (since it's the first segment):
<circle stroke-dashoffset="0" stroke-dasharray="30 100" cx="50%" cy="50%" r="15.9154943092" fill="none" stroke-width="31.8309886184" />
Adding pie segments:
You'll need to decrement (as we need negative values) the dash-offset values progressively by subtracting the previous dash length (percentage):
0, -30, -90
Example: 60% dash length; offset. -30
<circle stroke-dashoffset="-30" stroke-dasharray="60 100" cx="50%" cy="50%" r="15.9154943092" fill="none" stroke-width="31.8309886184" />
Example optimized for reusability (using css variables)
.icon-wrp {
position: relative;
display: inline-block;
width: 200px;
vertical-align: top;
}
.chart {
width: 1em;
height: 1em;
font-size: var(--chartFontSize);
}
.segment {
stroke-dasharray: var(--percent) 100;
stroke-dashoffset: var(--offset);
stroke: var(--strokeColor);
}
.chartAni .segment {
animation-name: progress;
animation-fill-mode: forwards;
animation-delay: 0.3s;
animation-duration: 0.5s;
transition: 0.3s;
stroke-dasharray: 0 100;
}
@keyframes progress {
from {
stroke-dasharray: 0 100;
stroke-dashoffset: 0;
}
to {
stroke-dasharray: var(--percent) 100;
stroke-dashoffset: var(--offset);
}
}
<!-- pie asset – hidden -->
<svg class="svgPieAsset" style="display:none" viewBox="0 0 63.6619772368 63.6619772368">
<symbol id="slice" viewBox="0 0 63.6619772368 63.6619772368">
<circle transform="rotate(-90 31.8309886184 31.8309886184)" id="circle" class="percent" cx="31.8309886184" cy="31.8309886184" r="15.9154943092" fill="none" stroke-width="31.8309886184" />
</symbol>
</svg>
<!-- visible pie chart -->
<div class="icon-wrp">
<svg id="pieChart01" class="chart chartAni" style="--chartFontSize:20vw">
<use class="segment" href="#slice" style="--offset:-0; --percent:33.333; --strokeColor:green" />
<use class="segment" href="#slice" style="--offset:-33.333; --percent:33.333; --strokeColor:purple" />
<use class="segment" href="#slice" style="--offset:-66.666; --percent:33.333; --strokeColor:gray" />
</svg>
</div>
Edit: donut chart example
For a donut chart or a circular gauge – just adjust the stroke-width to your needs.
.icon-wrp {
position: relative;
display: inline-block;
width: 200px;
vertical-align: top;
}
.chart {
width: 1em;
height: 1em;
font-size: var(--chartFontSize);
}
.segment {
stroke-dasharray: var(--percent) 100;
stroke-dashoffset: var(--offset);
stroke: var(--strokeColor);
}
.chartAni .segment {
animation-name: progress;
animation-fill-mode: forwards;
animation-delay: 0.3s;
animation-duration: 0.5s;
transition: 0.3s;
stroke-dasharray: 0 100;
}
@keyframes progress {
from {
stroke-dasharray: 0 100;
stroke-dashoffset: 0;
}
to {
stroke-dasharray: var(--percent) 100;
stroke-dashoffset: var(--offset);
}
}
<!-- pie asset – hidden -->
<svg class="svgPieAsset" style="display:none;" >
<symbol id="slice" viewBox="0 0 33 33">
<circle id="circle" class="percent" cx="50%" cy="50%" r="15.9154943092" fill="none" stroke-width="0.95" />
</symbol>
</svg>
<!-- visible pie chart -->
<div class="icon-wrp">
<svg id="pieChart01" class="chart chartAni" style="--chartFontSize:20vw; transform:rotate(-90deg);">
<use class="segment" href="#slice" style="--offset:0; --percent:10; --strokeColor:blue" />
<use class="segment" href="#slice" style="--offset:-10; --percent:30; --strokeColor:red" />
<use class="segment" href="#slice" style="--offset:-40; --percent:60; --strokeColor:yellow" />
</svg>
</div>
Upvotes: 6