Elfayer Elfayer - 6 months ago 42
HTML Question

How to show sections on a radial progress bar?

I currently have a radial progress bar like a 'solid' border and I'd like to have a radial progress bar with sections (like a 'dashed' border but with more control on it).

What I'm looking for :

enter image description here

Here is what I have so far:



var el = document.getElementById('graph'); // get canvas

var options = {
percent: el.getAttribute('data-percent'),
size: el.getAttribute('data-size'),
lineWidth: el.getAttribute('data-line'),
rotate: 0
}

var canvas = document.createElement('canvas');
var span = document.createElement('span');
span.textContent = options.percent + '%';

if (typeof(G_vmlCanvasManager) !== 'undefined') {
G_vmlCanvasManager.initElement(canvas);
}

var ctx = canvas.getContext('2d');
canvas.width = canvas.height = options.size;

el.appendChild(span);
el.appendChild(canvas);

ctx.translate(options.size / 2, options.size / 2); // change center
ctx.rotate((-1 / 2 + options.rotate / 180) * Math.PI); // rotate -90 deg

var radius = (options.size - options.lineWidth) / 2;

var drawCircle = function(color, lineWidth, percent) {
percent = Math.min(Math.max(0, percent || 1), 1);
ctx.beginPath();
ctx.arc(0, 0, radius, 0, Math.PI * 2 * percent, false);
ctx.strokeStyle = color;
ctx.lineCap = 'round'; // butt, round or square
ctx.lineWidth = lineWidth
ctx.stroke();
};

drawCircle('#57d39d', options.lineWidth, 100 / 100);
drawCircle('#14928e', options.lineWidth, options.percent / 100);

div {
position: relative;
margin: 30px;
width: 110px;
height: 110px;
}
canvas {
display: block;
position: absolute;
top: 0;
left: 0;
}
span {
color: black;
display: block;
line-height: 110px;
text-align: center;
width: 110px;
font-family: sans-serif;
font-weight: bold;
font-size: 30px;
margin-left: 5px;
}

<div class="chart" id="graph" data-percent="30" data-size="110" data-line="6"></div>




K3N K3N
Answer

You can quite literally use a dash pattern. Just calculate the dash pattern based on the circle's circumference:

Assuming the variables are declared:

radius = 140;
circum = 2 * Math.PI * radius;
gap = 7;
sections = 5;
dashOn = circum / sections - gap;

Then set the dash-pattern this way:

ctx.setLineDash([dashOn, gap]);    // set dash pattern
ctx.lineDashOffset = -gap * 0.5;   // center dash gap

I you want to have the first gap/section pointing up you just need to rotate the context -90° first.

Update: If you want rounded caps for each segment, simply active this by doing:

ctx.lineCap = "round";

However, the "cap" will consume part of the gap so we need to compensate the size of the gap by adding the line width to the gap value.

Example

Shows how to set and remove the dash-pattern, and demonstrates how to rotate the context so that the sections are drawn from the top position, as well as how to make rounded caps.

var ctx = c.getContext("2d"),
    radius = 140,
    circum = 2 * Math.PI * radius,
    lineWidth = 12,
    gap = 9 + lineWidth,                         // compensate for rounded caps
    sections = 5,
    dashOn = circum / sections - gap;

function render() {
  var t = +s.value / sections;                   // normalize value on sections

  ctx.clearRect(0, 0, 300, 300);                 // clear previous drawn content
  ctx.setTransform(1,0,0,1, 150, 150);           // translate to center
  ctx.rotate(-Math.PI*0.5);                      // rotate -90° so 0° is up

  ctx.beginPath();
  ctx.arc(0,0,radius, Math.PI*2*t, Math.PI*2);   // circle from angle x t
  ctx.setLineDash([dashOn, gap]);                // set dash pattern
  ctx.lineDashOffset = -gap * 0.5;               // center dash gap
  ctx.lineWidth = lineWidth;                     // line width
  ctx.lineCap = "round";                         // line width
  ctx.strokeStyle = "#9ac";                      // base color
  ctx.stroke();                                  // render it
  
  ctx.beginPath();
  ctx.arc(0, 0, radius, 0, Math.PI*2 * t);       // render arc based on angle x t
  ctx.strokeStyle = "#06c";                      // top color
  ctx.stroke();

  ctx.setLineDash([]);                           // reset dash
  ctx.setTransform(1,0,0,1,0,0);                 // reset transforms
  // render text here
}

(s.oninput = render)();                          // demo slider and first run
<label>Sections: <input id=s type=range min=0 max=5 value=0></label><br>
<canvas id=c height=300></canvas>