Progress Bar en forma de Arco

Las progress bar habitualmente consisten en una barra de color que va aumentando o disminuyendo de longitud mientras ocurre algo: cargar un fichero, recorrer un form multistep... En esta aplicación te presento lo que podriamos llamar una progress arc en lugar de una barra creciente se usa un arco de circunferencia, o un sector circular, para ilustrar el progreso de una tarea.

Solución

<script>

/*Progress bar con forma de arco o sector circular*/

/*Convertir polares (radio, ángulo) a cartesianas (x, y)*/

function polarToCartesian(centerX, centerY, radius, angleInDegrees) {

var angleInRadians = (angleInDegrees-90) * Math.PI / 180.0;

return {

x: centerX + (radius * Math.cos(angleInRadians)),

y: centerY + (radius * Math.sin(angleInRadians))

};

}

/*Consturir el path del arco*/

function describeArc(x, y, radius, startAngle, endAngle){

var start = polarToCartesian(x, y, radius, endAngle);

var end = polarToCartesian(x, y, radius, startAngle);

var largeArcFlag = endAngle - startAngle <= 180 ? "0" : "1";

var d = [

"M", start.x, start.y,

"A", radius, radius, 0, largeArcFlag, 0, end.x, end.y

].join(" ");

return d;

}

/*Clase para un arco, los datos se obtienen del elemento del DOM con id = ident */

class ProgressArc{

    constructor(ident){

    let container = document.getElementById(ident);

    this.end = parseInt(container.dataset.value);

    this.end = (this.end > 100)?100:this.end;

    this.radio = parseInt(window.getComputedStyle(container).height)/2;

    this.text = container.getElementsByTagName('div')[0];

    this.arc = container.getElementsByTagName('svg')[0];

    this.actual = 0

    this.arc.style.height = this.radio*2+'px';

    }

/*Animación */

play(){

    this.timer = setInterval(this.draw.bind(this), 25)

}

/*Dibuja el arco*/

draw(){

    let path = this.arc.children[0]

    let angle = 0;

    this.actual++;

    angle = this.actual*3.599;

    if (this.actual >= this.end){

    clearInterval(this.timer)

    path.setAttribute("d", describeArc(this.radio, this.radio, this.radio, 0, angle));

    }

else

    path.setAttribute("d", describeArc(this.radio, this.radio, this.radio, 0, angle));

    this.text.innerText = this.actual+"%";

    }

}

/*Crear los objetos para cada progressArc de la página*/

    window.onload = function() {

    var arco = new ProgressArc('arco');

    arco.play();

}

</script>

<style>

*{

  margin: 0;

  padding: 0;

  font-family: 'Arial', sans-serif;

}

/*Para centrar en la página*/

html, body{

   display:grid;

   height:100%;

   text-align: center;

   place-items: center;

   background: #dde6f0;

}

.progressArc{

  position: relative;

  display: flex;

  align-content: center;

  align-items: center;

  justify-content: center;

  text-align: center;

  height: 250px;

  width: 250px;

}

.progressArc div{

z-index:1;

}

.progressArc svg{

  background: rgb(134, 237, 81);

  border-radius: 50%;

  aspect-ratio: 1 / 1;

  height: 100%;

  position: absolute;

  top: 0;

  left: 0;

  z-index:0;

}

</style>

<body>

<div class="progressArc" id="arco" data-value = "75">

<div></div>

<svg><path fill="none" stroke="#446688" stroke-width="20"/></svg>

</div>

</body>

Explicación

Las progress bar son habituales para indicar el grado en que se ha alcanzado un objetivo, o el porcentaje de progreso de una tarea.

Esta app implementa un progress Arc, o sea, que en lugar de una barra se mostrará un arco de circunferencia creciente, o un sector circular.

La aplicación se basa en un gráfico SVG dinámico, es decir que se va a modificar mediante un script, que dibujará un arco.

El elemento SVG contiene el color del trazo en el atributo stroke y el grosor del mismo, en stroke-width. Si este ancho se coloca igual al ancho del contenedor del SVG tendremos un sector circular en lugar de un arco.

El elemento SVG se coloca en forma absoluta dentro de su contenedor para lograr una alineación adecuada.

En el body se debe poner un bloque div (el contenedor del SVG) que contendrá otro div para mostrar el avance de la barra en número (0,1%, 2%...)  y el SVG que dibuja el arco cambiante.

El contenedor tiene la clase progressArc y un dataset (data-valor) para indicar el porcentaje del círculo que se rellenará. La clase es la responsable de la dimensión con las propiedades height width, y de la alineación del texto con el SVG (colocar el texto en el centro).

En el script se crea una clase desde la que se podrán instanciar  uno o más Progress Arc. Está pensado para poder usar varios en una mismo bloque, por tanto con la misma rapidez y estilos.