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.
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:
Firefox seems to have problems, rendering svg elements when attributes are are calculated via css calc() (SVG pie-chart working only in Chrome, not in Firefox)
difficult to be reused for multiple pi chart instances
Recommendations:
simplify your calculations by optimizing your svg geometry.
control your values (e.g pie percentages, colors) via HTML/svg attributes (or css variables)
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>
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>
Computing the paths
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.
Computing the coordinates
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
Connect the coordinates using SVG path commands:
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.
Related
I am trying to look for a way to achieve a simple progress circle (static) with no animations. The examples I have found have very different offsets for percentage such as given in the example below. How do I make my progress circle in such a way that if I provide offset as 50%, then it is exactly 50% (half filled)?
.u-absoluteCenter {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
margin: auto;
}
.u-flexCenter {
display: flex;
align-items: center;
justify-content: center;
}
.u-offscreen {
position: absolute;
left: -999em;
}
.demo {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
margin: auto;
display: flex;
align-items: center;
justify-content: center;
}
.progress {
transform: rotate(-90deg);
}
.progress__value {
stroke-dasharray: 0;
stroke-dashoffset: 0;
}
#-webkit-keyframes progress {
from {
stroke-dashoffset: 339.292;
}
to {
stroke-dashoffset: 0;
}
}
#keyframes progress {
from {
stroke-dashoffset: 339.292;
}
to {
stroke-dashoffset: 0;
}
}
<svg width="120" height="120" viewBox="0 0 120 120">
<circle cx="60" cy="60" r="54" fill="none" stroke="#e6e6e6" stroke-width="12" />
<circle cx="60" cy="60" r="54" fill="none" stroke="#f77a52" stroke-width="12"
stroke-dasharray="339.292" stroke-dashoffset="339.292" />
</svg>
You can leverage an SVG attribute to set the path length rather than having to calculate it.
pathLength sets the length to whatever you need...say 100 for a progress bar.
The pathLength attribute lets authors specify a total length for the path, in user units. This value is then used to calibrate the browser's distance calculations with those of the author, by scaling all distance computations using the ratio pathLength/(computed value of path length).
pathLength="100"
Then you can set the stroke-dasharray to 100 as well and then adjust the stroke-dashoffset as needed....
::root {
--val: 0;
}
svg {
transform: rotate(-90deg);
}
.percent {
stroke-dasharray: 100;
stroke-dashoffset: calc(100 - var(--val));
}
.fifty {
--val: 50;
}
.sixty {
--val: 60;
}
.ninety {
--val: 90;
}
<svg width="120" height="120" viewBox="0 0 120 120">
<circle cx="60" cy="60" r="54" fill="none" stroke="#e6e6e6" stroke-width="12" />
<circle class="percent fifty" cx="60" cy="60" r="54" fill="none" stroke="#f77a52" stroke-width="12" pathLength="100" />
</svg>
<svg width="120" height="120" viewBox="0 0 120 120">
<circle cx="60" cy="60" r="54" fill="none" stroke="#e6e6e6" stroke-width="12" />
<circle class="percent sixty" cx="60" cy="60" r="54" fill="none" stroke="#f77a52" stroke-width="12" pathLength="100" />
</svg>
<svg width="120" height="120" viewBox="0 0 120 120">
<circle cx="60" cy="60" r="54" fill="none" stroke="#e6e6e6" stroke-width="12" />
<circle class="percent ninety" cx="60" cy="60" r="54" fill="none" stroke="#f77a52" stroke-width="12" pathLength="100" />
</svg>
As Paulie says, pathLength is the key to progress circles
A modern Custom Element (supported in all modern browsers) makes for a re-usable HTML Element
<svg-progress-circle percent="30"></svg-progress-circle>
<svg-progress-circle percent="20" color="blue"></svg-progress-circle>
<svg-progress-circle percent="80" color="gold"></svg-progress-circle>
Added a range-input for interactive demo purposes.
Percent is a property on the element, you can set with code like:
document.getElementById("Slider1").percent = <PERCENTAGE>;
If you don't want a dashed grey fullcircle, delete the dash setting from the pathLenght=120 path
I used a path instead of overlapping circles because with some other settings the almost same code can create pie-charts.
<style>
svg { width: 150px; background: teal }
svg-progress-circle[percent="100"] path { stroke: green }
</style>
<svg-progress-circle percent="30"></svg-progress-circle>
<svg-progress-circle percent="20" color="blue"></svg-progress-circle>
<svg-progress-circle percent="80" color="gold"></svg-progress-circle>
<script>
customElements.define("svg-progress-circle", class extends HTMLElement {
connectedCallback() {
let d = 'M5,30a25,25,0,1,1,50,0a25,25,0,1,1,-50,0'; // circle
this.innerHTML =
`<input type="range" min="0" max="100" step="10" value="30"`+ // delete 2 lines
` oninput="this.parentNode.percent=this.value" /><br>`+ // just for demo
`<svg viewBox="0 0 60 60">
<path stroke-dasharray="10 2" stroke-dashoffset="-19"
pathlength="120" d="${d}" fill="grey" stroke="lightgrey" stroke-width="5"/>
<path stroke-dasharray="30 70" stroke-dashoffset="-25"
pathlength="100" d="${d}" fill="none"
stroke="${this.getAttribute("color")||"red"}" stroke-width="5"/>
<text x="50%" y="57%" text-anchor="middle">30%</text></svg>`;
this.style.display='inline-block';
this.percent = this.getAttribute("percent");
}
set percent(val = 0) {
this.setAttribute("percent", val);
let dash = val + " " + (100 - val);
this.querySelector("path+path").setAttribute('stroke-dasharray', dash);
this.querySelector("text").innerHTML = val + "%";
this.querySelector("input").value = val;
}
})
</script>
Note: I am working on a complete Web Component that does Pie Graphs and fancy Progress circles like:
But, it is one of many side-projects... HTML examples and obfuscated source code available at https://pie-meister.github.io/
I'm attempting to use :hover in CSS to get a slice of a circle to rotate.
The CSS I'm attempting to use will cause the slice to rotate as I would expect. However, when I add the :hover selector, it doesn't rotate.
My HTML and CSS is below:
transform-origin: 190px 50px;
transform: rotate(360deg);
transition: transform 10s;
}
#box:hover {
fill-opacity: 0%;
stroke: black;
}
#circle:hover {
transform-origin: 10% 10%;
transform: rotate(90deg);
transition: transform 2s;
stroke: black;
}
<div id="divThree">
<svg id="box" version="1.1" width="500" height="300" viewBox="0 0 500 300" xmlns="htp://www.w3.org/2000/svg">
<g class="slice">
<path d="M 50 50 H 190 V 190 C 190 190, 58.99 189.99, 50 50"></path>
</g>
<circle id="circle" cx="190" cy="50" r="140" fill-opacity="0.0" stroke="red "fill="black"/>
</svg>
</div>
My codepen is here.
Your CSS code is fine. You can do #box .slice:hover.
But you should look at your SVG: there is <g class="slice"> before <circle .... So circle overlaps your slice, in this case slice will never be hovered.
Simplest solution will be to just move circle before slice:
<svg id="box" version="1.1" width="500" height="300" viewBox="0 0 500 300" xmlns="htp://www.w3.org/2000/svg">
<circle id="circle" cx="190" cy="50" r="140" fill-opacity="0.0" stroke="red "fill="black"/>
<g class="slice">
<path d="M 50 50 H 190 V 190 C 190 190, 58.99 189.99, 50 50"></path>
</g>
</svg>
or you can disable pointer events for circle:
#box circle {
pointer-events: none;
}
Working solution: https://codepen.io/emtei/pen/PoqObRp?editors=1100
I have an SVG tag in my html files, and I have co-ordinates that I want to basically animate one path, and when it finishes, animate the second. But both paths animate starting at the same time, and I'm not sure why.
I've tried putting the co-ordinates in the path, with M to move then L to make the line, then another M which I thought would start the 2nd path, etc.
Here is my path:
<defs>
<path id="path1" d="M1400 1520 L1260 1480 M1280 480 L1110 460 L1060 260 L1180 240 " />
<mask id="mask1"><use class="mask" xlink:href="#path1"></mask>
</defs>
<use class="paths" xlink:href="#path1" mask="url(#mask1)" />
and here is .css to animate:
.paths {
fill: none;
stroke: black;
stroke-dasharray: 12;
stroke-width: 5;
stroke-linejoin: round;
}
.mask {
fill: none;
stroke: white;
stroke-width: 10;
stroke-dasharray: 1000;
stroke-dashoffset: 1000;
animation: dash 5s linear alternate infinite;
}
/* does not work in IE, need JS to animate there */
#keyframes dash {
from {
stroke-dashoffset: 1000;
}
to {
stroke-dashoffset: 0;
}
}
...as you can see the 1st path draws just 1 line, then I would like to move to the start of the 2nd line (M1280) but this starts animating as soon as the line at M1400 for some reason
To animate two lines separately, you need to divide the common path into two paths:
<path id="path1" fill="none" stroke="black" d="M1400 1520 L1260 1480" />
<path id="path2" fill="none" stroke="red" d="M1280 480 L1110 460 L1060 260 L1180 240" />
To get the lines in view, I used the command transform = "translate (x y)"
You can specify your coordinates X Y for positioning depending on your needs.
To animate line drawing, the stroke-dashoffset attribute is used.
For id = "path1" the length of the line is 148px
For id = "path2" the length of the line is 499px
The animation of the second line begins after the end of the animation of the first line begin ="an1.end"
<svg version="1.1" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
width="1800" height="1800" viewBox="0 0 1800 1800">
<path id="path1" fill="none" stroke="black" stroke-width="5"
transform="translate(-1100 -1200)"
stroke-dashoffset="148"
stroke-dasharray="148 148"
d="M1400 1520 L1260 1480" >
<animate id="an1"
attributeName="stroke-dashoffset"
begin="0s;an2.end"
dur="2s"
values="148;0;148"
fill="freeze" />
</path>
<path id="path2" fill="none" stroke="red" stroke-width="5"
transform="translate(-1000 -220)"
stroke-dashoffset="499"
stroke-dasharray="499 499"
d="M1280 480 L1110 460 L1060 260 L1180 240" >
<animate id="an2"
attributeName="stroke-dashoffset"
begin="an1.end"
dur="2s"values="499;0;499"
from="499"
to="0"
fill="freeze" />
</path>
</svg>
CSS animation
#path1 {
fill:none;
stroke:black;
stroke-width: 5;
stroke-linejoin: round;
stroke-dashoffset:148;
stroke-dasharray:148;
animation: dash1 1.5s linear alternate infinite;
}
#keyframes dash1 {
from {
stroke-dashoffset: 148;
}
to {
stroke-dashoffset: 0;
}
}
#path2 {
fill:none;
stroke:red;
stroke-width: 5;
stroke-linejoin: round;
stroke-dashoffset:499;
stroke-dasharray:499;
animation: dash2 3s linear alternate infinite;
animation-delay:3s;
}
#keyframes dash2 {
from {
stroke-dashoffset: 499;
}
to {
stroke-dashoffset: 0;
}
}
<svg version="1.1" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
width="1800" height="1800" viewBox="0 0 1800 1800">
<path id="path1" transform="translate(-1100 -1200)" d="M1400 1520 L1260 1480" >
</path>
<path id="path2" transform="translate(-1000 -220)"
d="M1280 480 L1110 460 L1060 260 L1180 240" >
</path>
</svg>
This question already has answers here:
CSS Progress Circle
(8 answers)
Closed 4 years ago.
I try to design a circle progressbar with some information inside. Something like this.
I have svg but I cant write inside circle also. The start and end point distance is very low. I am looking for something like image.
svg {
height: 200px;
margin: auto;
display: block;
}
path {
stroke-linecap: round;
stroke-width: 2;
}
path.grey {
stroke: lightgrey;
}
path.purple {
stroke: purple;
stroke-dasharray: calc(40 3.142 1.85);
stroke-dashoffset: 80;
/ adjust last number for variance /
}
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewbox="0 0 100 100">
<path class="grey" d="M40,90
A40,40 0 1,1 70,90"
style="fill:none;"/>
<path class="purple" d="M40,90
A40,40 0 1,1 70,90"
style="fill:none;"/>
</svg>
This is my solution; In order to calculate the path's length you may use the path.getTotalLength() method.
In order to center the text around a point (the center of the SVG canvas in this case) use dominant-baseline="middle" text-anchor="middle"
theRange.addEventListener("input",()=>{
let v=220.6 - map(theRange.value,0,100,0,220.6);
thePath.style.strokeDashoffset = v
theText.textContent = theRange.value+"%"
})
function map(n, a, b, _a, _b) {
let d = b - a;
let _d = _b - _a;
let u = _d / d;
return _a + n * u;
}
svg {
height: 200px;
margin: auto;
display: block;
border:1px solid;
overflow:visible
}
path {
stroke-linecap: round;
stroke-width: 2;
}
.grey {
stroke: lightgrey;
}
.purple {
stroke: purple;
stroke-dasharray: 220.6;
stroke-dashoffset: 44.12;
}
p{text-align:center}
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 110 110">
<defs>
<path id="thePath" d="M40,90
A40,40 0 1,1 70,90"
style="fill:none;"/>
</defs>
<use xlink:href="#thePath" id="base" class="grey" />
<use xlink:href="#thePath" id="slider" class="purple" />
<text id="theText" x="55" y="55" dominant-baseline="middle" text-anchor="middle">80%</text>
</svg>
<p><input id="theRange" type="range" min="0" max="100" value="80" step=".1" /></p>
I would like to have an percent circle indicator on my site:
In this case it's showing 75%. How should this be done? I have the yellow circle in a image-file, but if it's easier to, some how, do it all using CSS, that's okay with me.
Considering the shape of the progress bar (rounded end/start) I would suggest using SVG.
DEMO: Radial progress bar
In the following example, the progress is animated with the stroke-dasarray attribute and the % numbers are incremented with jQuery:
var count = $(('#count'));
$({ Counter: 0 }).animate({ Counter: count.text() }, {
duration: 5000,
easing: 'linear',
step: function () {
count.text(Math.ceil(this.Counter)+ "%");
}
});
body{text-align:center;font-family: 'Open Sans', sans-serif;}
svg{width:25%;}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<svg id="animated" viewbox="0 0 100 100">
<circle cx="50" cy="50" r="45" fill="#FDB900"/>
<path fill="none" stroke-linecap="round" stroke-width="5" stroke="#fff"
stroke-dasharray="251.2,0"
d="M50 10
a 40 40 0 0 1 0 80
a 40 40 0 0 1 0 -80">
<animate attributeName="stroke-dasharray" from="0,251.2" to="251.2,0" dur="5s"/>
</path>
<text id="count" x="50" y="50" text-anchor="middle" dy="7" font-size="20">100%</text>
</svg>
Unfortunatly IE doesn't support svg SMIL animations. To achieve the same result with IE support, you can use a library like snap.svg and animate the stroke-dasharray attribute with JS :
var count = $(('#count'));
$({ Counter: 0 }).animate({ Counter: count.text() }, {
duration: 5000,
easing: 'linear',
step: function () {
count.text(Math.ceil(this.Counter)+ "%");
}
});
var s = Snap('#animated');
var progress = s.select('#progress');
progress.attr({strokeDasharray: '0, 251.2'});
Snap.animate(0,251.2, function( value ) {
progress.attr({ 'stroke-dasharray':value+',251.2'});
}, 5000);
body{text-align:center;font-family:sans-serif;}
svg{width:25%;}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/snap.svg/0.3.0/snap.svg-min.js"></script>
<svg id="svg" viewbox="0 0 100 100">
<circle cx="50" cy="50" r="45" fill="#FDB900"/>
<path fill="none" stroke-linecap="round" stroke-width="5" stroke="#fff"
stroke-dasharray="1,250.2"
d="M50 10
a 40 40 0 0 1 0 80
a 40 40 0 0 1 0 -80"/>
<text x="50" y="50" text-anchor="middle" dy="7" font-size="20">1%</text>
</svg>
<svg viewbox="0 0 100 100">
<circle cx="50" cy="50" r="45" fill="#FDB900"/>
<path fill="none" stroke-linecap="round" stroke-width="5" stroke="#fff"
stroke-dasharray="125.6,125.6"
d="M50 10
a 40 40 0 0 1 0 80
a 40 40 0 0 1 0 -80"/>
<text x="50" y="50" text-anchor="middle" dy="7" font-size="20">50%</text>
</svg>
<svg id="animated" viewbox="0 0 100 100">
<circle cx="50" cy="50" r="45" fill="#FDB900"/>
<path id="progress" stroke-linecap="round" stroke-width="5" stroke="#fff" fill="none"
d="M50 10
a 40 40 0 0 1 0 80
a 40 40 0 0 1 0 -80">
</path>
<text id="count" x="50" y="50" text-anchor="middle" dy="7" font-size="20">100%</text>
</svg>
Pure HTML/CSS Solution
In short, I used the CSS border-radius and z-index properties to create a white half circle layered beneath an orange center circle and orange outer ring. Initially, the half-circle is completely hidden behind an additional layer of orange on the left side, but its outer edge is gradually revealed as it rotates around the center axis (via CSS transform: rotate()), creating the appearance of a progress bar. Additional trickery is required once the progress bar passes the halfway point (see below the snippet for more info).
All of this was done with pure HTML and CSS, except the animation, which uses JavaScript. It may appear to require more code than the SVG-based solutions, but the markup is actually much simpler, which makes it a good alternative in my opinion.
function setProgress(elem, percent) {
var
degrees = percent * 3.6,
transform = /MSIE 9/.test(navigator.userAgent) ? 'msTransform' : 'transform';
elem.querySelector('.counter').setAttribute('data-percent', Math.round(percent));
elem.querySelector('.progressEnd').style[transform] = 'rotate(' + degrees + 'deg)';
elem.querySelector('.progress').style[transform] = 'rotate(' + degrees + 'deg)';
if(percent >= 50 && !/(^|\s)fiftyPlus(\s|$)/.test(elem.className))
elem.className += ' fiftyPlus';
}
(function() {
var
elem = document.querySelector('.circlePercent'),
percent = 0;
(function animate() {
setProgress(elem, (percent += .25));
if(percent < 100)
setTimeout(animate, 15);
})();
})();
.circlePercent {
position: relative;
top: 26px;
left: 26px;
width: 96px;
height: 96px;
border-radius: 50%;
background: orange;
}
.circlePercent:before,
.circlePercent > .progressEnd {
position: absolute;
z-index: 3;
top: 2px;
left: 45px;
width: 6px;
height: 6px;
border-radius: 50%;
background: white;
-ms-transform-origin: 3px 46px;
transform-origin: 3px 46px;
content: "";
}
.circlePercent:after,
.circlePercent > .progress {
position: absolute;
-ms-transform-origin: 48px 48px;
transform-origin: 48px 48px;
z-index: 0;
top: 0;
left: 0;
width: 48px;
height: 96px;
border-radius: 48px 0 0 48px;
background: orange;
content: "";
}
.circlePercent.fiftyPlus:after {
background: white;
-ms-transform: rotate(180deg);
transform: rotate(180deg);
}
.circlePercent > .progress.progress {
background: white;
}
.circlePercent > .counter {
position: absolute;
box-sizing: border-box;
z-index: 2;
width: 100px;
height: 100px;
margin-top: -2px;
margin-left: -2px;
border-radius: 50%;
border: 4px solid orange;
}
.circlePercent > .counter:before {
position: absolute;
z-index: 1;
top: 50%;
margin-top: -13px;
width: 100%;
height: 26px;
font-size: 26px;
line-height: 26px;
font-family: sans-serif;
text-align: center;
color: white;
content: attr(data-percent) "%";
}
.circlePercent > .counter:after {
position: absolute;
width: 80px;
height: 80px;
top: 6px;
left: 6px;
border-radius: 50%;
background: orange;
content: "";
}
.circlePercent > .counter[data-percent="100"] {
background: white;
}
<div class="circlePercent">
<div class="counter" data-percent="0"></div>
<div class="progress"></div>
<div class="progressEnd"></div>
</div>
Notice that the fiftyPlus class is added to the container element once the progress bar reaches the halfway point. This adds a static progress bar to the right half of the circle. Without it, the progress will appear to drain out of the right side instead of filling in the left side.
Demo : CODEPEN
Here is my attempt at getting the percentage circle indicator:
Path draws two arches and i set the stroke-dasharray.
we don't really need to set the dasharray since we set that later with javascript.
Html
<svg class="circles" viewbox="0 0 200 200">
<g id="first">
<circle r="50" cx="100" cy="100" fill="#fb0"/>
<path fill="none"
stroke-linecap="round"
stroke-width="7"
stroke="#ffa"
stroke-dasharray="250,250"
d="M100 60
A1 1 0 0 1 100 140
A1 1 0 0 1 100 60"/>
<text class="circ-text"
text-anchor="middle"
x="100" y="100"
font-size="12px"
fill="#ffa"
>percentage
</text>
</g>
</svg>
Javascirpt
Get the path with #first path.
Get the length of the dash-array: path.getTotalLength();
Increase the dash-array until its full: setAttribute('stroke-dasharray', i+","+length); Where i is increased.
Find out what percentage we are at: (count++/ticks)*100
add the percentage to the svg text: text.innerHTML=(count/tick)*100
var path = document.querySelector('#first path');
var text = document.querySelector('#first .circ-text');
var length = path.getTotalLength();
var i = 0;
var count = 0;
var ticks = 50;
setInterval(function() {
if (i < length+length/ticks) {
path.setAttribute('stroke-dasharray', i+","+length);
i+=length/ticks;
text.innerHTML=Math.round((count++/ticks)*100);
}
}, 100);
var path = document.querySelector('#first path');
var text = document.querySelector('#first .circ-text');
var length = path.getTotalLength();
var i = 0;
var count = 0;
var ticks = 50;
setInterval(function() {
if (i < length+length/ticks) {
path.setAttribute('stroke-dasharray', i+","+length);
i+=length/ticks;
text.innerHTML=Math.round((count++/ticks)*100);
}
}, 100);
<svg class="circles" viewbox="0 0 500 200">
<g id="first">
<circle r="50" cx="100" cy="100" fill="#fb0" />
<path fill="none" stroke-linecap="round" stroke-width="7" stroke="#ffa" stroke-dasharray="250,250" d="M100 60 A1 1 0 0 1 100 140
A1 1 0 0 1 100 60" />
<text class="circ-text" text-anchor="middle" x="100" y="100" font-size="12px" fill="#ffa">percentage</text>
</g>
Circular Percent Progress bar using Canvas ...
var ctx = document.getElementById('circularLoader').getContext('2d');
var al = 0;
var start = 4.72;
var cw = ctx.canvas.width;
var ch = ctx.canvas.height;
var diff;
function progressSim(){
diff = ((al / 100) * Math.PI*2*10).toFixed(2);
ctx.clearRect(0, 0, cw, ch);
ctx.lineWidth = 17;
ctx.fillStyle = '#4285f4';
ctx.strokeStyle = "#4285f4";
ctx.textAlign = "center";
ctx.font="28px monospace";
ctx.fillText(al+'%', cw*.52, ch*.5+5, cw+12);
ctx.beginPath();
ctx.arc(100, 100, 75, start, diff/10+start, false);
ctx.stroke();
if(al >= 100){
clearTimeout(sim);
// Add scripting here that will run when progress completes
}
al++;
}
var sim = setInterval(progressSim, 50);
<div id="loader">
<canvas id="circularLoader" width="200" height="200"></canvas>
</div>
See Demo :- http://codingflag.in/mysketch.php?sketch=9
Have you tried this?
<percent-display percent="75" side="" colors=""></percent-display>
Source: http://angularscript.com/angularjs-round-percentage-bar-directive-ngpercentdisplay/