Gráficos de porcentaje: iniciados con Javascript & animados y formateados con CSS 14.1.16
En esta nueva colaboración Furoya se centra en trabajar con gráficos tanto de barras, como circulares y otras variantes: cómo obtener sus valores con Javascript y representarlos (formato y estilos, animación incluida) mediante CSS.
Gráficos de porcentaje: iniciados con Javascript & animados y formateados con CSS
Ya probamos bastantes mecanismos (entre métodos y engendros) como para empezar a aplicarlos en casos propios y ajenos. Hace unos meses encontré por ahí una consulta sobre gráficos de porcentaje animados, y me extrañó que alguien tuviese problemas para encontrarlos. Aún suponiendo que no sepa CSS (o hasta un poco de JS si debe capturar valores de texto para incluir en los estilos) debe haber mil ejemplos de algo tan sencillo.
Ni pensemos en lenguajes gráficos como SVG, o en el viejo flash, o hasta canvas
, porque es evidente que no vamos a encontrar mayores dificultades para dibujar y luego animar cualquier cosa. Pero supuse que meter un valor de porcentaje en algún elemento y darle formato de gráfico con animación CSS era una tarea fácil.
La verdad es que sí lo es, hasta que tratamos con ejemplos muy específicos, como los de torta (pastel), rosca (anillo), arco o línea. Y ni hablar de los radiales, 3D, ... o Internet Explorer.
Hace tiempo puse en Codepen un par de "editores" hechos con javascript, que escribían el código HTML y CSS para un single-element que contenía un gráfico de torta o uno de línea.
Editor de gráfico de torta. - Editor de gráfico de líneas.
Insisto con el detalle: el desafío era crear un gráfico en un solo elemento con CSS puro, en algún otro lenguaje es realmente fácil, si están diseñados para "dibujar".
Como en la comunidad de G+ Puro CSS sólo demos amenacé con agregar los de barra y columna —pero nunca cumplí—, ahora aprovecho el espacio que me da Kseso y los muestro en el blog, con animación incluída.
See the Pen Gráficos de porcentaje animados. by solipsistaCP (@solipsistacp) on CodePen.
Nota: si se perdieron el movimiento, usando el botón "Rerun" se puede actualizar el marco para verlo de nuevo. Si están usando Internet Explorer, ni se molesten, en ese navegador las transiciones de fondos no se ven.
Antes de entrar en detalles, supongo que habrán notado algo muy evidente. Los arcos no tienen animación en ningún navegador. Fue justamente el formato que me abrió los ojos al por qué estaban pidiendo por él en algún foro, ya que tiene más de una complicación en cualquiera de los métodos para crearlo y animarlo. Nos vamos a ocupar de esto después, ahora veamos las barras y columnas.
El javascript comentado es el siguiente.
function ajusta() {
/*PONE EN UNA VARIABLE TODO EL LIST ITEM PARA BARRAS */
var horizontales = document.querySelectorAll("#graficos .horizontal");
/*PONE EN UNA VARIABLE TODO EL LIST ITEM PARA COLUMNAS */
var torres = document.querySelectorAll("#graficos #vertical .torre");
/*PONE EN UNA VARIABLE TODO EL LIST ITEM PARA ARCOS */
var arcos = document.querySelectorAll("#graficos .arco");
var elem = 0; // VARIABLE DE INICIO DE CONTEO
/* RECORRE LOS LIST ITEM PARA BARRAS HASTA LLEGAR AL ÚLTIMO */
while(elem < horizontales.length) {
/* AGREGA A CADA UNO EL ESTILO INLINE PARA ANIMACIÓN DEL FONDO DE COLOR */
horizontales[elem].style.transition = "background-size 3s ease-in-out 2s";
/* AGREGA A CADA UNO EL ESTILO PARA EL TAMAÑO FINAL DEL FONDO; EL ANCHO SE CAPTURA DEL CONTENIDO DE CADA ELEMENTO */
horizontales[elem].style.backgroundSize = horizontales[elem].innerHTML+" 100%";
/* SUMA "1" A LA VARIABLE DE CONTEO EN CADA VUELTA */
elem++;
};
elem = 0; //REINICIA LA VARIABLE PARA EL PRÓXIMO CONTEO
/* RECORRE LOS LIST ITEM PARA COLUMNAS HASTA LLEGAR AL ÚLTIMO */
while(elem<torres.length) {
/* AGREGA A CADA UNO EL ESTILO INLINE PARA ANIMACIÓN DEL FONDO DE COLOR; ADEMÁS CAMBIA EL TIEMPO DE RETARDO PARA CADA UNO */
torres[elem].style.transition = "background-size 1s ease-out "+(elem+4)+"s";
/* AGREGA A CADA UNO EL ESTILO PARA EL TAMAÑO FINAL DEL FONDO; EL ALTO SE CAPTURA DEL CONTENIDO DE CADA ELEMENTO */
torres[elem].style.backgroundSize = "80% "+ torres[elem].innerHTML;
/* SUMA "1" A LA VARIABLE DE CONTEO EN CADA VUELTA */
elem++;
};
elem = 0; //REINICIA LA VARIABLE PARA EL PRÓXIMO CONTEO
/* RECORRE LOS LIST ITEM PARA ARCOS HASTA LLEGAR AL ÚLTIMO */
while(elem<arcos.length) {
/* AGREGA A CADA UNO EL ESTILO PARA EL GIRO DEL FONDO; LOS GRADOS SE CAPTURAN DEL CONTENIDO DE CADA ELEMENTO Y SE CONVIERTEN DESDE EL PORCENTAJE */
arcos[elem].style.backgroundImage = "radial-gradient(circle at bottom, white 45%, transparent 47%, transparent 67%, white 70%), linear-gradient("+ (parseInt(arcos[elem].innerHTML) / 100) * 180 +"deg, green 50%, transparent 50%)";
/* SUMA "1" A LA VARIABLE DE CONTEO EN CADA VUELTA */
elem++;
};
}
onload = ajusta; //EJECUTA LA FUNCIÓN CUANDO SE CARGA LA PÁGINA
El código explicado
- Los dos primeros son practicamente iguales. Y en todos buscamos la "colección" (más exactamente, los "list item") de los elementos que tengan la misma clase para aplicar el formato específico. Estos elementos son unos
figure
(pueden ser cualquiera) y contienen sin ninguna etiqueta el valor en porcentaje que tendrá su tamaño. - Una novedad es que ya no usamos un
for()
para recorrerlas, sino que aprovechamos para practicar un poco conwhile(){}
. Como su nombre lo indica, repite en un loop lo que pongamos entre sus llaves mientras se cumpla la condición entre paréntesis. Pero en este caso, la variable de conteo va afuera, y la declaramos antes. La adición en cada vuelta va junto con las instrucciónes a ejecutar. Por eso vamos a ver después que si la usamos en otrowhile
hay que volverla siempre a cero. O tener más de una con distintos nombres. - En cada vuelta se le agrega al elemento un estilo inline. Primero la transición, que como no tiene dos valores distintos para aplicar, no cambia nada. Luego se pone un
background-size
que va a modificar el alto o ancho dependiendo de si queremos hacer una barra o una columna para el gráfico. El valor de ese porcentaje de tamaño se captura del contenido del elemento, ya que será exactamente el mismo que tiene puesto dentro. Si queremos mostrar un 50%, escribimos "50%" y elinnerHTML
va a leer ese texto para pasarlo como un valor de tamaño. Apenas aparece esa nueva medida, se dispara la animación CSS que habíamos agregado antes. Del resto de los formatos se encarga la hoja de estilos. - Una vez terminada la modificación del primer elemento de la lista, se incrementa la variable
elem
en "1" y mientras siga siendo menor al total de elementos, pasa a la otra vuelta, ahora para hacer los nuevos cambios en el siguiente ítem. Cuando termina con todos los de su clase, elwhile
se detiene; y antes de ejecutar el siguiente se vuelve la variable de conteo a cero, o empezaría a contar desde el último número que le dejó el bucle anterior. - El método para los arcos ya no tiene animación, y aún así merece que detallemos un poco cómo está armado el CSS. Cada elemento es un rectángulo de 2 de ancho por 1 de alto. El "arco" es una capa de fondo hecha con gradientes circulares de la misma medida que su caja, con un semicírculo opaco desde abajo, seguido de un arco transparente y terminado con el mismo color opaco. Detrás de este fondo, creamos otra imagen, esta vez de 2 por 2, cuyo centro coincide con el medio de la linea base del elemento rectangular, y el gradiente formará dos colores, la mitad de arriba en el tono base y abajo el color destacado. Esto es el default, con
0deg
, pero si cambiamos el ángulo ese fondo va a rotar en sentido horario y por la izquierda va a asomar el color destacado que "quedó abajo", en la mitad que no se veía; si el porcentaje es de 50%, el ángulo debería quedar en 90deg (la mitad de 100% y la mitad de 180deg respectivamente), y el efecto termina dejando ver por la mitad izquierda del arco transparente un color destacado, y por la derecha el color que elegimos de "base". El truco está en usar una fórmula para convertir el número limpio del contenido (el caracter "%" se elimina conparseInt
) en su equivalente para los 180° disponibles. Luego se pone ese valor en elradial-gradient
que corresponde a la imagen de atrás, y ya tenemos el arco de color con la medida igualada al porcentaje escrito. - Por último encontramos al ya conocido
onload
que es quien va a ejecutar la función al cargarse el documento.
Como en IExplorer los fondos no se pueden animar (al menos hasta la publicación de éste artículo), para hacer compatible el efecto en barras y columnas hay que prescindir de los gradientes. Se pueden usar los pseudoelementos para crear el porcentaje coloreado (más abajo vemos una versión), bordes o sombras. Aquí les dejo uno de estos últimos como muestra, sin explicaciones porque al final es muy sencillo.
See the Pen Gráficos de porcentaje. (para IE.) by solipsistaCP (@solipsistacp) on CodePen.
Ahora sí, vamos a lo entretenido.
Ánimo hasta llegar al arco. [*↓]
El problema para animar el arco mencionado arriba es evidente : esa cantidad de valores que entran en los gradientes de background-image
no son animatable
. Por supuesto que ya sabemos cómo animar paso a paso en JS usando setTimeout()
, pero sería el último recurso, vamos a inventar otra cosa para seguir usando transition
de CSS.
Una solución posible es crear otro elemento con los dos colores dentro de figure
en posición absoluta, delante un elemento más con el arco transparente y adelante de todo dejar el texto. Así, con elementos separados, podemos rotar el coloreado con transform:rotate(ndeg)
y conseguir el mismo efecto que con la capa
de imagen de fondo. Pero la idea no es hacerlo fácil, si desde el principio mencioné al single-element, seguiremos entonces con la misma política.
Seguramente ya se imaginaran a qué vamos a echarle mano. El pseudoelemento ::before
se puede usar como capa de fondo
. Hay que darle un formato cuadrado de 2 por 2 y "dibujarle" con gradientes un círculo opaco desde el centro, seguido de una corona circular transparente y terminado con otra del mismo color opaco hasta el borde. Detrás de esta capa va otro fondo, con la mitad de arriba del color de base y la de abajo con el color destacado.
En la práctica nos queda un cuadrado que tiene el dibujo de un círculo (digamos mejor, una circunsferencia de trazo grueso) con el medio arco superior de un color y el inferior de otro. Pero dentro del elemento, sólo se ve el superior (con una ayudita del overflow:hidden
), porque mide 2 de ancho por solamente 1 de alto.
Y ahora se supone que va a ser fácil rotar el pseudo usando transform
. Pero, no. Aunque, al menos, ya no es imposible.
Ya no tenemos al arquero en el piso. [*↓]
Veamos la demo de este ejemplo.
See the Pen Gráficos de porcentaje en arco. by solipsistaCP (@solipsistacp) on CodePen.
Aquí tenemos un pseudoelemento como el descripto más arriba, con algún parche en z-index
que otrora se comentó en este blog.
El nuevo problema que nos creamos ahora es que como cada elemento tiene su propio valor escrito dentro, el CSS se aplicaba inline uno por uno con su medida. Ahora tenemos que modificar un pseudo ... al que no se puede acceder inline desde un elemento HTML.
La única opción es agregarlo a la hoja de estilos; que no supone al fin tanto trabajo, porque ya sabemos hacerlo de artículos anteriores. Lo que resulta increíble pero necesario, es que haya que agregar una regla para cada elemento que encuentre el querySelectorAll(".arco")
, es decir un .arco::before{} para cada gráfico, que seguro tiene un porcentaje diferente.
Sí, yo también lo pensé. No va a funcionar, porque la última clase que escribamos se va a aplicar a todos los arcos.
A menos que usemos :nth-...
o pongamos un id
propio por elemento, y que el pseudo con su formato acorde vaya en ese selector.
Sé que suena a mucho, y el código resultante en el HTML que procesa el navegador nos daría pena si pudiésemos verlo. Pero el javascript es mucho más corto de lo que se supone.
function gira() {
/* TODOS LOS ELEMENTOS */
var arcos = document.querySelectorAll("#graficos .arco");
elem = 0; //VARIABLE DE INICIO PARA CONTEO
/* RECORRE TODOS LOS ELEMENTOS */
while(elem < arcos.length) {
/* NOMBRE ID CREADO PARA CADA ELEMENTO */
var identificador = "e13m3nt0"+elem;
/* CÁLCULO DE CONVERSIÓN PORCENTAJE A GRADOS */
var grados = (parseInt(arcos[elem].innerHTML) / 100) * 180;
/* AGREGA AL FINAL DE PRIMER HOJA DE ESTILO LA REGLA PARA EL ID CREADO */
document.styleSheets[0].insertRule("#"+ identificador +"::before {transition: transform 3s cubic-bezier(.3,.2,.45,1.5) 1s; transform: rotate("+ grados +"deg)}", document.styleSheets[0].cssRules.length);
/* AGREGA AL ELEMENTO EL MISMO ID */
arcos[elem].setAttribute("id",identificador);
/* INCREMENTA VAR DE CONTEO */
elem++;
};
}
onload = gira; //EJECUTA FUNCIÓN AL CARGAR EL DOCUMENTO
Hay más comentario que código. ;-)
- El mecanismo para recorrer los elementos es el mismo. Los cambios vienen dentro del loop.
- Lo primero que se crea es un nombre de identificador que debe ser único para cada elemento, (al final vamos a usar ese método). El modo más práctico es poner un texto seguido de un número que no se repita. En vez de usar un valor sacado del reloj, que siempre es distinto por eso de la entropía del tiempo (aunque el fatalismo de Nietzsche con su "Eterno retorno" o el determinismo de Platón con sus "Años platónicos" me dejan dudando), nosotros aprovechamos la variable incremental, que a cada vuelta suma "1" y por lo tanto no van a aparecer dos iguales.
- Luego se captura el texto con el porcentaje y se lo convierte a número para calcular su equivalencia en grados.
- Después se ubica la primera hoja de estilos del documento (aunque podría y hasta debería ser la última) y al final se inserta la regla con el nuevo identificador y su
::before
. Allí se escribe toda la transición, además de la rotación, con los grados calculados previamente. [Nota: si la reglatransition
es igual para todos los arcos, debería estar en el CSS; la dejo en el javascript para que puedan probar animaciones en secuencia, o con distintas velocidades calculadas a partir del porcentaje. ] - Ahora se busca el elemento correspondiente, y se le agrega el atributo
id
con el mismo 'texto seguido de número' que usamos en la hoja de estilo. Una vez que javascript pone el identificador, CSS comienza la animación. - Aumenta la variable
elem
en "1", que —tal como dijimos— agregada al texto identificador en la siguiente vuelta va a crear unid
diferente para el nuevo elemento.
Solos, apuntando al arco. [*↓]
Otro paso más sería crear gráficos interactivos. Hasta ahora suponemos que los valores dentro de cada elemento son escritos por un lenguaje de servidor y sacados de alguna base de datos. La página nos ofrece la lista de porcentajes, y en el navegador les damos formato y animación.
Pero también existe la posibilidad de que permitamos al usuario el ingreso de datos y que vea sus propios gráficos.
Una versión conocida, porque ya animamos una calavera con input type="range"
, sería ésta:
See the Pen Gráfico de porcentaje en arco, interactivo. by solipsistaCP (@solipsistacp) on CodePen.
Algunos pasos son distintos, porque los valores de entrada están hechos para grados (0 a 180), y la conversión se hace al porcentaje fijado con 2 decimales para mostrarlos dentro del elemento. Es nada más otra manera de hacer las cosas, aunque en formularios no es muy práctica, porque el valor que tenemos que enviar es el del input
.
Ya que llegaron hasta aquí, les dejo otro interactivo de yapa. Son input
's de texto que muestran dentro el valor escrito y su gráfico.
Lo más útil de estos es su validación, ya que no sólo utiliza pattern
con expresiones regulares, sino que además muestra cómo se captura con JS el resultado de la validación para cambiar una variable a cero (y entonces borra el gráfico) cuando se sale del rango permitido.
No tiene habilitada la animación, para no molestar en las pruebas que hagan.
See the Pen Gráfico de porcentaje en barra, interactivo. by solipsistaCP (@solipsistacp) on CodePen.
Por último, una aclaración repetida : en los ejemplos suelo usar código estandarizado. Sabemos que hay navegadores que tienen su sintaxis propietaria para javascript (o JScript, je) y entonces habrá que agregar condicionales para aplicar uno u otro código dependiendo del navegador donde se vea. No siempre estará hecho en estos artículos simplemente porque no son para cutanpastiar sino para estudiar los mecanismos y aplicarlos en sus proyectos.
* En el fútbol, meta, portería, valla, arco, son sinónimos de la zona de gol. Para la mayoría de estos títulos abusé de la sinonimia entre el último nombre y los arcos del gráfico simplemente de molesto que soy. No hay segunda intención ni mensaje satánico oculto.
Crédito y autoría del artículo
Artículo original de Furoya.
La intención del autor con sus colaboraciones no es que los artículos sirvan para hacer un copy&paste de códigos sino que comprendas y aprendas la lógica y el cómo trabaja javaScript.
Y a partir de lo expuesto experimentes tú.
El autor del post y el editor del blog te animamos a que plantees tus dudas o reflexiones y que compartas tus realizaciones en base a lo expuesto en los comentarios. Recuerda que puedes incluir pens (ejemplos en Codepen.io) en ellos.
Furoya, genial como siempre, se agradece mucho la explicación!
ResponderEliminarGracias a vos, Lisandro, por la exageración.
ResponderEliminarHabría que explorar más posibilidades de diseño, como que en vez de una barra se mueva un field, o una aguja.
Recordemos que este artículo es deudor de otros como Gráficos circulares, lineales y de barras en puro Css y sus predecesores. Todo lo que hago es seguir dando vueltas al asunto.
Un saludo.
Muchas gracias! ¡Es genial! También puede probar la función de gráficos a partir de shieldui, que yo he descubierto recientemente, mira aquí: https://demos.shieldui.com/web/line-chart/axis-marker. Se puede personalizar tantas veces como quiera.
ResponderEliminarDe nada.
EliminarSupongo que habrás espameado ese sitio con la mejor intención. Pero la idea es justamente evitar el uso (mejor, 'abuso') de frameworks para efectos que, si se entiende el mecanismo, son muy fáciles de hacer con unas pocas líneas de código. El que publicaste (y ni hacía falta verlo, pero yo lo hice) no sólo usa su propio escript sino que además carga un JQuery. Es un despropósito. Lo que no quise ver es cómo genera las gráficas. Probablemente con SVG, que sería lo lógico.
Aprovecho para recordar un ejemplo que publiqué en la comunidad y que usando javascript genera un gráfico puro CSS. Es una curiosidad y hay que entender CSS, obviamente, para editar el código que ofrece la herramienta y pegarlo donde corresponde. (Lo veo y todavía me parece increíble que funcione :-P).
[pen] data-height="265" data-theme-id="0" data-slug-hash="JIFvH" data-default-tab="html,result" data-user="solipsistacp" data-embed-version="2" data-preview="true" class="codepen"[/pen]
Gracias por esta explicación! Yo no sabía cómo funciona, realmente no soy tan bueno con código, en todo caso. Me gustó el resultado final y la forma en que en esta biblioteca ShieldUI puede hacer su trabajo de gráficos a través de diferentes tecnologías. Busco un buen plugin gráfico de mi sitio y, literalmente, he cavado a través de cientos de ellos. Estoy confundido como resultado. Realmente aprecio su solución de HTML y CSS puro, por lo que entiendo es la solución más ligera.
EliminarBueno, más que nada es una rareza. Es ligera, sí, pero el "valor" (por llamar a su mérito de alguna forma) es que termina siendo un gráfico single element dibujado con CSS puro.
EliminarNo te recomiendo usarlo en un sitio de producción. En realidad es como casi todo lo que publico : para desarmar y estudiar.
(Y de paso voltear otro impoCSSible).
Un saludo, y gracias a vos.