Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Circular progress show more than 100%

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:

enter image description here

<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;
}
like image 631
Jakubbb1337 Avatar asked Mar 03 '23 02:03

Jakubbb1337


2 Answers

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>

Update

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>

option with CSS animation of progress equal to 119%

#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

like image 96
Alexandr_TT Avatar answered Mar 17 '23 04:03

Alexandr_TT


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:

  • Mark the percentage with the start of the dash (0 0 4 146 to 0 119 4 27).
  • Extend the 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.
like image 34
vqf Avatar answered Mar 17 '23 06:03

vqf