4

I am attempting to create a pie chart using css and html. I would simply be displaying a few static numbers therefore I am trying to keep it relatively simple and not use any animations.

I'm currently running into a road block on how to create my desired look. The code snippet below works exactly as I would like it to, the issue with this is that conic-gradient is not supported with firefox and internet explorer which will be an issue with this project.

.progress-circle {
  --d: 50px;
  --color: #002F65;
  --progress: 40;
  border-radius: var(--d);
  height: var(--d);
  width: var(--d);
  background: conic-gradient( var(--color) calc(calc(var(--progress) / 100) * 360deg), transparent calc(calc(var(--progress) / 100) * 360deg));
}
<div class="progress-circle"></div>

I have been searching for an alternative that resembles the example above which had lead me to this article: designing simple pie charts with css

My issue with this is that the way to calculate the percenage growth of the pie chart seems to be not compatible with what I am trying to accomplish. as it is determined by transform: rotate(.1turn);

My main question is it possible to make conic-gradient compatible with other browsers? If not, what would be the best way to approach making a pie chart with css to closely resemble the first example?

For context I will be passing data from an array to determine the percentage of the pie chart.

.pie {
  width: 100px; height: 100px;
  border-radius: 50%;
  background: yellowgreen;
  background-image:
  linear-gradient(to right, transparent 50%, #655 0);
}

.pie::before {
  content: "";
  display: block;
  margin-left: 50%;
  height: 100%;
  border-radius: 0 100% 100% 0 / 50%;
  background: #655;
  transform-origin: left;
  transform: rotate(.1turn);
}
<div class="pie"></div>
stepheniok
  • 395
  • 3
  • 16
  • check this: https://stackoverflow.com/a/52205730/8620333 (remove the top layer and you get what you want) – Temani Afif Jul 15 '20 at 22:20
  • @TemaniAfif I apprecicate your response. Your example helps with what i'm looking for, but I have a few questions as you are the one who posted the answer. How would I pass values of the percentage? It seems like this is determined by the degrees, for example; if I wnated to pass `40%` to cover 40% . I've provided a jsfiddle link for as an example: https://jsfiddle.net/94jrmkdc/20/ – stepheniok Jul 15 '20 at 22:43

2 Answers2

5

Here is an idea based on this previous answer

.box {
  /* percentage to degree
    --s:0 for [0% 50%]
    --s:1 for [50% 100%]
   */
  --v:calc( ((18/5) * var(--p) - 90)*1deg);

  width:100px;
  height:100px;
  display:inline-block;
  border-radius:50%;
  background:
    linear-gradient(var(--v), yellowgreen 50%,transparent 0) 0 /calc((1 - var(--s))*100%),
    linear-gradient(var(--v), transparent 50%,#655        0) 0 /calc(var(--s)*100%),
    linear-gradient(to right, yellowgreen 50%,#655 0);
}
<div class="box" style="--p:5;--s:0"></div>
<div class="box" style="--p:20;--s:0"></div>
<div class="box" style="--p:50;--s:0"></div>
<div class="box" style="--p:70;--s:1"></div>
<div class="box" style="--p:95;--s:1"></div>

We can optimize the code using min() and keep the use of only one variables but you need to pay attention to the support: https://caniuse.com/#feat=css-math-functions

.box {
  /* percentage to degree  */
  --v:calc( ((18/5) * var(--p) - 90)*1deg);

  width:100px;
  height:100px;
  display:inline-block;
  border-radius:50%;
  background:
    linear-gradient(var(--v), yellowgreen 50%,transparent 0) 0 /min(100%,(50 - var(--p))*100%),
    linear-gradient(var(--v), transparent 50%,#655        0) 0 /min(100%,(var(--p) - 50)*100%),
    linear-gradient(to right, yellowgreen 50%,#655 0);
}
<div class="box" style="--p:5;"></div>
<div class="box" style="--p:20;"></div>
<div class="box" style="--p:50;"></div>
<div class="box" style="--p:70;"></div>
<div class="box" style="--p:95;"></div>

Another idea using pseudo element with more support:

.box {
  /* percentage to degree  */
  --v: calc( ((18/5) * var(--p) - 180)*1deg);
  
  width: 100px;
  display: inline-flex;
  border-radius: 50%;
  overflow: hidden;
  background: linear-gradient(to right, yellowgreen 50%, #655 0);
}

.box::before,
.box::after {
  content: "";
  width: 50%;
  padding-top:100%;
  transform: rotate(var(--v));
}

.box::before {
  background: 
    linear-gradient(yellowgreen 0 0) 
    0 / calc((50 - var(--p))*1%);
  transform-origin: right;
}

.box::after {
  background: 
    linear-gradient(#655 0 0)       
    0 / calc((var(--p) - 50)*1%);
  transform-origin: left;
}
<div class="box" style="--p:5;"></div>
<div class="box" style="--p:20;width:150px;"></div>
<div class="box" style="--p:50;width:120px;"></div>
<div class="box" style="--p:70;"></div>
<div class="box" style="--p:95;width:80px;"></div>

CSS pie chart

Temani Afif
  • 245,468
  • 26
  • 309
  • 415
1

CSS solution

Based on changing the parameters of the stroke-dasharray attribute

To set the desired attribute values in accordance with the opening angle of the sector, you need to calculate the total circumference at the selected radius.

Let's say the radius is 50px

let radius = 50;
 let circumference = radius * 2 * Math.PI;
 console.log(circumference );

Full circumference ~= 314px

For example, to draw a segment equal to a quarter of a circle:

calculate the length of the dash: 314 * 0.25 = 78.5px

gap length: 314 * 0.75 = 235.5px

Formula for stroke-dasharray: stroke-dasharray="78.5, 235.5"

Similarly, stroke-dasharray parameters are set for other corners of sectors.

circle {
fill:#665555;
} 
#p15,#p90,#p180,#p270 {
fill:none;
stroke:#9ACD32;
stroke-width:100;
}
#p15 {
stroke-dasharray:15.7,298.3;
}
#p90 {
stroke-dasharray:78.5,235.5;
}
#p180 {
stroke-dasharray:157,157;
}
#p270 {
stroke-dasharray:235.5,78.5;
}
<svg width="200" height="200" style="border:1px solid">
   <circle id="bg" r="100" cx="100" cy="100"  />  
       
   <path id="p15"    d="M100,50A50,50 0 0 1 100,150A50,50 0 0 1 100,50">
  </path>
</svg>   
           <!-- 90deg -->
   <svg width="200" height="200" style="border:1px solid">
       <circle  r="100" cx="100" cy="100" />  
            
       <path id="p90"  stroke-dasharray="78.5,235.5"  d="M100,50A50,50 0 0 1 100,150A50,50 0 0 1 100,50">
       </path>
   </svg> 
           <!-- 180deg -->
   <svg width="200" height="200" style="border:1px solid">
       <circle  r="100" cx="100" cy="100"  />  
            
       <path id="p180"  d="M100,50A50,50 0 0 1 100,150A50,50 0 0 1 100,50">
       </path>
   </svg>   
           <!-- 270deg -->
<svg width="200" height="200" style="border:1px solid">
       <circle  r="100" cx="100" cy="100"  />  
            
       <path id="p270"    d="M100,50A50,50 0 0 1 100,150A50,50 0 0 1 100,50">
       </path>
</svg>

SVG solution

As in the example CSS solution uses changing the stroke-dasharray attributes

<svg width="200" height="200">
  <circle id="bg" r="100" cx="100" cy="100"  fill="#665555"/> 
 
  <path stroke-dasharray="300 14" stroke-dashoffset="300" d="M100,50A50,50 0 0 1 100,150A50,50 0 0 1 100,50" id="p1" 
     r="50" cx="100" cy="100" stroke="#9ACD32" stroke-width="100" fill="none"  >
  </path>
 </svg>
   <svg width="200" height="200">
      <circle  r="100" cx="100" cy="100"  fill="#665555"/> 
          <path stroke-dasharray="235.5 78.5" stroke-dashoffset="235.5" d="M100,50A50,50 0 0 1 100,150A50,50 0 0 1 100,50" stroke="#9ACD32" stroke-width="100" fill="none" > 
          </path>
   </svg> 
<svg width="200" height="200">
      <circle id="bg" r="100" cx="100" cy="100"  fill="#665555"/> 
          <path stroke-dasharray="157 157" stroke-dashoffset="157" d="M100,50A50,50 0 0 1 100,150A50,50 0 0 1 100,50"  stroke="#9ACD32" stroke-width="100" fill="none"  >
      </path>
</svg>  
    <svg width="200" height="200">
      <circle id="bg" r="100" cx="100" cy="100"  fill="#665555"/> 
          <path stroke-dasharray="78.5 235.5" stroke-dashoffset="78.5" d="M100,50A50,50 0 0 1 100,150A50,50 0 0 1 100,50"  stroke="#9ACD32" stroke-width="100" fill="none"  >
      </path>
    </svg>   

An example of interactively changing the angle of a sector

The author's wishes:

For context I will be passing data from an array to determine the percentage of the pie chart.

enter image description here

This process is modeled using input and javascript:

  1. Displaying the percentage of filling the chart

let circumference = 50 * 2 * Math.PI,
 input = document.querySelector("[type='range']"),
 txt = document.querySelector("#txt1");

input.addEventListener("input",()=>{  
  pieChart();  
})

window.addEventListener("load",()=>{  
  pieChart();  
})

function pieChart(){
  let val = Number(input.value);
  let dash = circumference * val / 100;
  let gap = circumference - dash;
  p15.style.strokeDasharray = dash + " " + gap
txt.innerHTML = (val + '%'); 
}
<div><input id="size" step="1" type="range" min="0" max = "100" value="0" /></div>
<svg width="200" height="200" >
   <circle id="bg" r="100" cx="100" cy="100"  fill="#665555"/>  
       
   <path id="p15"  stroke-dasharray="15.7,298.3"  d="M100,50A50,50 0 0 1 100,150A50,50 0 0 1 100,50" id="p1" fill="none" stroke="#9ACD32" stroke-width="100" >
  </path> 
   <text id="txt1" y="60%" x="50%"  text-anchor="middle" font-size="32px" fill="white">0</text>
</svg>   
    
  1. Chart fill angle output

let circumference = 50 * 2 * Math.PI,
 input = document.querySelector("[type='range']"),
 txt = document.querySelector("#txt1");

input.addEventListener("input",()=>{  
  pieChart();  
})

window.addEventListener("load",()=>{  
  pieChart();  
})

function pieChart(){
  let val = Number(input.value);
  let dash = circumference * val / 360;
  let gap = circumference - dash;
  p15.style.strokeDasharray = dash + " " + gap
txt.innerHTML = (val); 
}
<div><input id="size" step="1" type="range" min="0" max = "360" value="0" /></div>
<svg width="200" height="200" >
   <circle id="bg" r="100" cx="100" cy="100"  fill="#665555"/>  
       
   <path id="p15"  stroke-dasharray="15.7,298.3"  d="M100,50A50,50 0 0 1 100,150A50,50 0 0 1 100,50" id="p1" fill="none" stroke="#9ACD32" stroke-width="100" >
  </path> 
   <text id="txt1" y="60%" x="50%"  text-anchor="middle" font-size="32px" fill="white">0</text>
</svg>   
    
Alexandr_TT
  • 13,635
  • 3
  • 27
  • 54