I made a circular chart like in this article: https://medium.com/@pppped/how-to-code-a-responsive-circular-percentage-chart-with-svg-and-css-3632f8cd7705
However, I also try to show values above 100% on it (the graph shows a comparison of consumption in the current month to the previous one and some times it needs to look like that). I tried to add another circle and opacity to the first one, but it doesn't look good.
I wish it looked liked this:
<svg viewBox="0 0 36 36" class="circular-chart cons">
<path class="circle-bg" d="M18 2.0845
a 15.9155 15.9155 0 0 1 0 31.831
a 15.9155 15.9155 0 0 1 0 -31.831" />
<path class="circle-round" stroke-dasharray="100 100"
style="opacity: 0.9;" d="M33.9155 18
a 15.9155 15.9155 0 0 1 -31.831 0
a 15.9155 15.9155 0 0 1 31.831 0" />
<path class="circle-round" stroke-dasharray="19 100" d="M33.9155 18
a 15.9155 15.9155 0 0 1 -31.831 0
a 15.9155 15.9155 0 0 1 31.831 0" />
<text x="18" y="20.35" class="percentage cons-fill">119%</text>
</svg>
.circle-bg {
fill: none;
stroke: #ddd;
stroke-width: 3.8;
}
.circle-round{
fill: none;
stroke-width: 2.8;
stroke-linecap: round;
animation: progress 1s ease-out forwards;
}
.circular-chart.cons .circle-round {
stroke: $cons;
}
@keyframes progress {
0% {
stroke-dasharray: 0 100;
}
}
.cons-fill {
fill: $cons;
}
Consider a solution - using two animations on different circles.
The start of both animations begins after clicking on the svg canvas begin="svg1.click"
First, the first animation works id="an_red"
, painting a circle in red in one second dur="1s"
.
After a full turn of the first animation id="an_red"
, the second animation id="an_green"
is turned on, which paints the entire green circle for 10 cycles of the red circle fill animation.
Please read the comments below in the code.
.circle-bg {
fill: none;
stroke: #ddd;
stroke-width: 3.8;
}
.circle-red{
fill: none;
stroke-width: 2.8;
stroke-linecap: round;
stroke:red;
stroke-dasharray:0 100;
}
.circle-green{
fill:none;
stroke: yellowgreen;
stroke-width: 2;
stroke-linecap: round;
stroke-dasharray:0 100;
}
#txt1 {
text-anchor:middle;
dominant-baseline:central;
font-size:6px;
fill:dodgerblue;
}
<svg id="svg1" width="80vw" height="80vh" viewBox="0 0 36 36" opacity="1" class="circular-chart cons">
<g id="gr1">
<!-- Gray circle -->
<path class="circle-bg" d="M18 2.0845
a 15.9155 15.9155 0 0 1 0 31.831
a 15.9155 15.9155 0 0 1 0 -31.831" />
<path class="circle-red"
style="opacity: 0.5;" d="M33.9155 18
a 15.9155 15.9155 0 0 1 -31.831 0
a 15.9155 15.9155 0 0 1 31.831 0" >
<!-- Red circle rotation animation -->
<animate
id="an_red"
attributeName="stroke-dasharray"
dur="1s"
values="0 100;100 0"
begin="svg1.click"
end="an_green.end-1s"
repeatCount="indefinite"
restart="whenNotActive" />
</path>
<circle class="circle-green"
cx="17.8155" cy="17.8155" r="14" >
<!-- Green circle rotation animation -->
<animate
id="an_green"
attributeName="stroke-dasharray"
begin="svg1.click+1s"
dur="10s"
values="0 100; 100 0"
repeatCount="1"
restart="whenNotActive"/>
</circle>
<text id="txt1" x="50%" y="50%" >Click me
<!-- Text fade animation -->
<animate attributeName="opacity" begin="svg1.click" dur="1s" to="0" fill="freeze" restart="whenNotActive" />
</text>
</g>
<animate xlink:href="#svg1" attributeName="opacity" values="1;0" begin="an_green.end" dur="1s" fill="freeze" />
</svg>
Consider the option when circles are located one above the other
The bottom circle is responsible for the animation showing the fill 100%
The upper circle of a brighter color is responsible for filling the remaining 19%.
The animation of this circle begins when the animation of the first, lower circle ends
<!-- Animation filling a dark red circle by 19% -->
<animate
id="an_dark_red"
attributeName="stroke-dasharray"
begin="an_red.end"
dur="1s"
values="0 100;19 81"
fill="freeze"
restart="whenNotActive" />
.circle-bg {
fill: none;
stroke: #ddd;
stroke-width: 3;
}
.circle-round{
fill: none;
stroke-width: 3;
stroke-linecap: round;
stroke-dasharray:0 100;
}
#txt1 {
text-anchor:middle;
dominant-baseline:central;
font-size:8px;
fill:red;
opacity:0;
}
<svg id="svg1" height="80vh" viewBox="0 0 36 36" class="circular-chart cons" >
<!-- gray circle background -->
<path class="circle-bg" class="circle-bg" d="M18 2.0845
a 15.9155 15.9155 0 0 1 0 31.831
a 15.9155 15.9155 0 0 1 0 -31.831" />
<path id="red" class="circle-round" stroke="#FFB5B5"
d="M33.9155 18
a 15.9155 15.9155 0 0 1 -31.831 0
a 15.9155 15.9155 0 0 1 31.831 0" >
<!-- Animation filling the red circle 100% -->
<animate
id="an_red"
attributeName="stroke-dasharray"
begin="svg1.click"
dur="4s"
values="0 100;100 0"
fill="freeze"
repeatCount="1"
restart="whenNotActive"/>
</path>
<path id="dark_red" class="circle-round" stroke="red"
stroke-dasharray="0 100"
d="M33.9155 18
a 15.9155 15.9155 0 0 1 -31.831 0
a 15.9155 15.9155 0 0 1 31.831 0" >
<!-- Animation filling a dark red circle by 19% -->
<animate
id="an_dark_red"
attributeName="stroke-dasharray"
begin="an_red.end"
dur="1s"
values="0 100;19 81"
fill="freeze"
restart="whenNotActive" />
</path>
<text id="txt1" x="50%" y="50%" >119%
<!-- The appearance of the text -->
<animate attributeName="opacity" begin="an_dark_red.end" dur="1s" to="1" fill="freeze" restart="whenNotActive" />
</text>
</svg>
#svg1 {
opacity:1;
height:90vh;
animation: hide 1s ease-out 7s forwards;
}
@keyframes hide {
100% {
opacity: 0;
}
}
.circle-bg {
fill: none;
stroke: #ddd;
stroke-width: 3;
}
.circle-round{
fill: none;
stroke-width: 3;
stroke-linecap: round;
stroke-dasharray:0 100;
}
#red {
animation: progress 4s ease-out forwards;
}
@keyframes progress {
100% {
stroke-dasharray: 100 0;
}
}
#dark_red {
animation: progress2 1s ease-out 3.9s forwards;
}
@keyframes progress2 {
100% {
stroke-dasharray: 19 81;
}
}
#txt1 {
text-anchor:middle;
dominant-baseline:central;
font-size:8px;
fill:red;
opacity:0;
animation: text_an 2s ease-out 5.1s forwards;
}
@keyframes text_an {
100% {
opacity: 1;
}
}
<svg id="svg1" height="30vh" viewBox="0 0 36 36" class="circular-chart cons" >
<!-- gray circle background -->
<path class="circle-bg" class="circle-bg" d="M18 2.0845
a 15.9155 15.9155 0 0 1 0 31.831
a 15.9155 15.9155 0 0 1 0 -31.831" />
<path id="red" class="circle-round" stroke="#FFB5B5"
d="M33.9155 18
a 15.9155 15.9155 0 0 1 -31.831 0
a 15.9155 15.9155 0 0 1 31.831 0" >
</path>
<path id="dark_red" class="circle-round" stroke="red"
stroke-dasharray="0 100"
d="M33.9155 18
a 15.9155 15.9155 0 0 1 -31.831 0
a 15.9155 15.9155 0 0 1 31.831 0" >
</path>
<text id="txt1" x="50%" y="50%" >119% </text>
</svg>
option with CSS animation of progress equal to 175%
#svg1 {
opacity:1;
animation: hide 3s ease-out 10s forwards;
}
@keyframes hide {
100% {
opacity: 0;
}
}
.circle-bg {
fill: none;
stroke: #ddd;
stroke-width: 3;
}
.circle-round{
fill: none;
stroke-width: 3;
stroke-linecap: round;
stroke-dasharray:0 100;
}
#red {
animation: progress 4s ease-out forwards;
}
@keyframes progress {
100% {
stroke-dasharray: 100 0;
}
}
#dark_red {
animation: progress2 3s ease-out 3.9s forwards;
}
@keyframes progress2 {
100% {
stroke-dasharray: 75 25;
}
}
#txt1 {
text-anchor:middle;
dominant-baseline:central;
font-size:8px;
fill:#CB5CCB;
opacity:0;
animation: text_an 2s ease-out 7s forwards;
}
@keyframes text_an {
100% {
opacity: 1;
}
}
<svg id="svg1" height="80vh" viewBox="0 0 36 36" class="circular-chart cons" >
<!-- gray circle background -->
<path class="circle-bg" class="circle-bg" d="M18 2.0845
a 15.9155 15.9155 0 0 1 0 31.831
a 15.9155 15.9155 0 0 1 0 -31.831" />
<path id="red" class="circle-round" stroke="#CBAACB"
d="M33.9155 18
a 15.9155 15.9155 0 0 1 -31.831 0
a 15.9155 15.9155 0 0 1 31.831 0" >
</path>
<path id="dark_red" class="circle-round" stroke="#CB5CCB"
stroke-dasharray="0 100"
d="M33.9155 18
a 15.9155 15.9155 0 0 1 -31.831 0
a 15.9155 15.9155 0 0 1 31.831 0" >
</path>
<text id="txt1" x="50%" y="50%" >175% </text>
</svg>
Note
CSS animation option works in all modern browsers including Edge
The problem is that you cannot go further than a full circle unless you extend the path. I agree that this is not the best tool to show these values, but you can do something like this:
:root{
--cons: #00ff00;
--atime: 1s;
}
.shade{
fill: none;
stroke: #002200;
stroke-width: 2.8;
/*stroke-linecap: round;*/
animation: sprogress var(--atime) ease-out forwards;
}
.circle-bg {
fill: none;
stroke: #ddd;
stroke-width: 3.8;
}
.circle-round{
fill: none;
stroke-width: 2.8;
stroke-linecap: round;
animation: progress var(--atime) ease-out forwards;
}
.circular-chart.cons .circle-round {
stroke: var(--cons);
}
@keyframes sprogress {
0% {
stroke-dasharray: 0 0 4 146;
}
}
@keyframes progress {
0% {
stroke-dasharray: 0 150;
}
}
.cons-fill {
fill: var(--cons);
font-size: 30%;
}
<svg viewBox="0 0 36 36" class="circular-chart cons" height="100vh">
<path class="circle-bg" d="M18 2.0845
a 15.9155 15.9155 0 0 1 0 31.831
a 15.9155 15.9155 0 0 1 0 -31.831" />
<path class="circle-round" stroke-dasharray="119 31" d="M33.9155 18
a 15.9155 15.9155 0 0 1 -31.831 0
a 15.9155 15.9155 0 0 1 31.831 0
a 15.9155 15.9155 0 0 1 -31.831 0 " />
<path class="shade" stroke-dasharray="0 115 4 31"
style="opacity: 0.9;" d="M33.9155 18
a 15.9155 15.9155 0 0 1 -31.831 0
a 15.9155 15.9155 0 0 1 31.831 0
a 15.9155 15.9155 0 0 1 -31.831 0 " />
<text x="18" y="20.35" class="percentage cons-fill">119%</text>
</svg>
The idea is that the path now has 150 units. To mark where the path is, you have a path with the class shade
. If you need more than 150, add more a
elements that keep on turning, and then calculate which values of stroke-dasharray
you need.
By the way, if you uncomment the stroke-linecap
in the shade
class, you will get a funny effect, as both ends will be displayed, independently of stroke-dasharray
.
EDIT after comment:
First, it is important to notice that the trick in the original code is that the perimeter of the circumpherence is 100. Therefore, each arc has 50 units. Regarding the stroke-dasharray
property, you can give it pairs of dash-gap values. If the sum of those values is equal to the perimeter of the arc, you can easily control the position and length of the dash. In the case of the shade
path, you animate from 0 0 4 146
(no dash, no gap, 4 units dash, 146 units gap) to 0 115 4 31
(no dash, 115 units gap, 4 units dash, 31 units gap). This way, the dash always has 4 units, and the end is at 115 + 4 = 119
units. This is not perfect, as the starting value (the end of the dash) is not 0, but 4. The effect is hardly noticeable, but it can be fixed in two ways:
0 0 4 146
to 0 119 4 27
).shade
path at the beginning, then change the stroke-dasharray
values. For instance, if you add another arc at the beginning the perimeter would be 200, and the values would go from 0 46 4 150
to 0 165 4 31
.If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With