Diversión con lienzo crea un complemento de gráficos de barras, parte 2

En esta serie de dos partes, combinaremos el versátil elemento de lienzo con la robusta biblioteca jQuery para crear un complemento de gráficos de barras. En esta segunda parte, lo convertiremos en un complemento de jQuery, y luego agregaremos un poco de dulce de ojos y características adicionales..

Concluyendo el Diversión con lienzo Serie de dos partes, hoy vamos a crear un complemento de gráficos de barras; no es un enchufe ordinario, fíjate. Vamos a mostrar algo de jQuery love al elemento canvas para crear un complemento muy robusto..

En la primera parte, analizamos únicamente la implementación de la lógica del complemento como un script independiente. Al final de la primera parte, nuestro gráfico de barras parecía tan.


Resultado al final de la Parte 1

En esta parte final, trabajaremos para convertir nuestro código y convertirlo en un complemento jQuery adecuado, agregar algunas mejoras visuales y, finalmente, incluir algunas funciones adicionales. En última instancia, nuestra salida se verá así:


Producto terminado

Todo calentado? Vamos a bucear!


Formalidades del plugin

Antes de comenzar a convertir nuestro código en un complemento, primero debemos analizar algunos trámites cuando se trata de la creación de complementos..


Nombrando el Plugin

Comenzamos eligiendo un nombre para el complemento. He elegido gráfico de barras y cambió el nombre del archivo JavaScript a jquery.barGraph.js. Ahora incluimos todo el código del artículo anterior dentro del siguiente fragmento de código.

 $ .fn.barGraph = function (settings) // code here

Ajustes contiene todos los parámetros opcionales pasados ​​al complemento.


Trabajando alrededor del problema de $ Symbol

En la creación de complementos de jQuery, generalmente se considera una práctica recomendada para usar jQuery en lugar del $ alias en su código, para minimizar conflictos con otras bibliotecas de Javascript. En lugar de pasar por todos esos problemas, podemos usar alias personalizados como se menciona en los documentos de jQuery. Adjuntamos todo nuestro código de complemento dentro de esta función anónima de ejecución automática, como se muestra a continuación:

 (función ($) $ .fn.barGraph = función (configuración) // código de implementación del complemento aquí) (jQuery);

Esencialmente, encapsulamos todo nuestro código dentro de una función y le pasamos jQuery. Tenemos la libertad de usar $ alias tanto como queramos dentro de nuestro código ahora, sin tener que preocuparnos de que pueda entrar en conflicto con otras bibliotecas de JavaScript.


Los valores por defecto

Al diseñar un complemento, es sensato exponer un número razonable de configuraciones al usuario, mientras se usan las opciones predeterminadas razonables si los usuarios usan el complemento sin pasarle ninguna opción. Con esto en mente, vamos a permitir que el usuario cambie cada una de las variables de opción de gráfico que mencioné en este artículo anterior de esta serie. Hacerlo es fácil; simplemente definimos cada una de estas variables como propiedades de un objeto y luego accedemos a ellas.

 var defaults = barSpacing = 20, barWidth = 20, cvHeight = 220, numYlabels = 8, xOffset = 20, maxVal, gWidth = 550, gHeight = 200; ;

Finalmente necesitamos fusionar las opciones predeterminadas con las opciones pasadas, dando preferencia a las pasadas. Esta línea se encarga de eso..

 var option = $ .extend (por defecto, configuración);

Recuerde cambiar los nombres de las variables donde sea necesario. Como en -

 return (param * barWidth) + ((param + 1) * barSpacing) + xOffset;

… cambios a:

 return (param * option.barWidth) + ((param + 1) * option.barSpacing) + option.xOffset;

Refactorización

Aquí es donde el plugin está martillado. Nuestra implementación anterior solo pudo producir un solo gráfico en una página, y la capacidad para crear múltiples gráficos en una página es la razón principal por la que estamos creando un complemento para esta funcionalidad. Además, debemos asegurarnos de que el usuario no necesite crear un elemento de lienzo para cada gráfico que se creará. Con esto en mente, vamos a crear los elementos del lienzo dinámicamente según sea necesario. Vamos a proceder. Veremos las versiones anteriores y actualizadas de las partes relevantes del código..


Invocando el Plugin

Antes de comenzar, me gustaría señalar cómo se invocará nuestro complemento.

 $ ("# años"). barGraph (barSpacing = 30, barWidth = 25, numYlabels = 12,);

Simple como eso. años Es el ID de la tabla que contiene todos nuestros valores. Pasamos las opciones según sea necesario..


Obtención de la fuente de datos

Para empezar, primero necesitamos una referencia a la fuente de datos para los gráficos. Ahora accedemos al elemento fuente y obtenemos su ID. Agregue la siguiente línea al grupo de variables de gráfico que declaramos anteriormente.

 var dataSource = $ (this) .attr ("id");

Definimos una nueva variable y le asignamos el valor del atributo de ID del elemento pasado. Dentro de nuestro codigo, esta se refiere al elemento DOM seleccionado actualmente. En nuestro ejemplo, se refiere a la tabla con un ID de años.

En la implementación anterior, el ID para la fuente de datos estaba codificado. Ahora lo reemplazamos con el atributo ID que extrajimos anteriormente. La versión anterior de la grabValues la función está abajo:

 function grabValues ​​() // Acceda a la celda de la tabla requerida, extraiga y agregue su valor a la matriz de valores. $ ("# data tr td: nth-child (2)"). each (function () gValues.push ($ (this) .text ());); // Acceda a la celda de la tabla requerida, extraiga y agregue su valor a la matriz xLabels. $ ("# data tr td: nth-child (1)"). each (function () xLabels.push ($ (this) .text ());); 

Se actualiza a esto:

 function grabValues ​​() // Acceda a la celda de la tabla requerida, extraiga y agregue su valor a la matriz de valores. $ ("#" + dataSource + "tr td: nth-child (2)"). each (function () gValues.push ($ (this) .text ());); // Acceda a la celda de la tabla requerida, extraiga y agregue su valor a la matriz xLabels. $ ("#" + dataSource + "tr td: nth-child (1)"). each (function () xLabels.push ($ (this) .text ());); 

Inyectando el elemento lienzo

 function initCanvas () $ ("#" + dataSource) .after (" "); // Intente acceder al elemento del lienzo cv = $ (" # bargraph - "+ dataSource) .get (0); if (! Cv.getContext) return; // Intente obtener un contexto 2D para el lienzo y lanza un error si no puede ctx = cv.getContext ('2d'); if (! ctx) return;

Creamos un elemento de lienzo y lo inyectamos en el DOM después de la tabla, que actúa como fuente de datos. jQuery después La función es muy útil aquí. Un atributo de clase de gráfico de barras y un atributo de ID en el formato barGraph-dataSourceID También se aplica para permitir que el usuario pueda personalizarlos como un grupo o individualmente, según sea necesario..


Ciclismo a través de los elementos pasados.

Hay dos formas de invocar este plugin en realidad. Puede crear cada gráfico por separado pasando solo una fuente de datos o puede pasar varias fuentes. En este último caso, nuestra construcción actual encontrará un error y se cerrará. Para rectificar esto, utilizamos el cada construir para iterar sobre el conjunto de elementos pasados.

 (function ($) $ .fn.barGraph = function (settings) // Variables de las opciones var defaults = // options here; // Combina los parámetros pasados ​​con la opción var de los valores predeterminados = $ .extend (valores predeterminados, configuraciones ); // Recorra cada objeto pasado this.each (function () // Código de implementación aquí); // Devuelve el objeto jQuery para permitir el encadenamiento. Return this;) (jQuery);

Encapsulamos todo el código después de obtener y fusionar las configuraciones dentro de esto.cada construir. También nos aseguramos de devolver el objeto jQuery al final para habilitar la capacidad de cadena..

Con esto, nuestra refactorización es completa. Deberíamos poder invocar nuestro complemento y crear tantos gráficos como sea necesario.


Añadiendo Eye Candy

Ahora que nuestra conversión está completa, podemos trabajar para mejorarla visualmente. Vamos a hacer una serie de cosas aquí. Miraremos a cada uno de ellos por separado..


Temas

La versión anterior usaba un gris suave para dibujar los gráficos. Vamos a implementar un mecanismo de tematización para las barras ahora. Esto, por sí mismo, consiste en una serie de pasos..


Océano: el tema por defecto
Follaje
flor de cerezo
Espectro

Añadiéndolo a las Opciones

 var defaults = // Otros valores predeterminados aquí tema: "Océano",;

Añadimos un tema opción a los valores predeterminados que permiten al usuario cambiar el tema a cualquiera de los cuatro ajustes preestablecidos disponibles.

Configuración del tema seleccionado actualmente

 function grabValues ​​() // Cambio de código anterior (option.theme) case 'Ocean': gTheme = thBlue; descanso; caso 'Follaje': gTheme = thGreen; descanso; caso 'Cherry Blossom': gTheme = thPink; descanso; caso 'Spectrum': gTheme = thAssorted; descanso; 

Un simple cambiar construye miradas al opción.tema ajuste y señala el gTema Variable a la matriz de colores necesaria. Usamos nombres descriptivos para los temas en lugar de los genéricos..

Definiendo la matriz de colores

 // Temas var thPink = ['#FFCCCC', '# FFCCCC', '# FFC0C0', '# FFB5B5', '# FFADAD', '# FFA4A4', '# FF9A9A', '# FF8989', '# FF6D6D ']; var thBlue = ['# ACE0FF', '# 9CDAFF', '# 90D6FF', '# 86D2FF', '# 7FCFFF', '# 79CDFF', '# 72CAFF', '# 6CC8FF', '# 57C0FF']; var thGreen = ['# D1FFA6', '# C6FF91', '# C0FF86', '# BCFF7D', '# B6FF72', '# B2FF6B', '# AAFE5D', '# A5FF51', '# 9FFF46']; var thAssorted = ['# FF93C2', '# FF93F6', '# E193FF', '# B893FF', '# 93A0FF', '# 93D7FF', '# 93F6FF', '# ABFF93', '# FF9B93'];

Luego definimos una serie de matrices, cada una con una serie de tonos de un color específico. Comienzan con el tono más claro y siguen aumentando. Iremos a través de estos arreglos más tarde. Agregar temas es tan simple como agregar una matriz para el color específico que necesita y luego modificar el anterior cambiar para reflejar los cambios.

La función de ayuda

 función getColour (param) return Math.ceil (Math.abs (((gValues.length / 2) -param))); 

Esta es una pequeña función que nos permite lograr y aplicar un efecto de degradado a los gráficos. Esencialmente, calculamos la diferencia absoluta entre la mitad del número de valores que se representarán y el parámetro pasado, que es el índice del elemento seleccionado actualmente en la matriz. De esta manera, somos capaces de crear un degradado suave. Dado que solo hemos definido nueve colores en cada una de las matrices de colores, estamos limitados a dieciocho valores por gráfico. Extender este número debería ser bastante trivial.

Configurando el estilo de relleno

 función drawGraph () for (index = 0; index 

Aquí es donde realmente hacemos el tema de los gráficos. En lugar de establecer un valor estático al estilo de relleno propiedad, usamos el obtenerColor Función para recuperar el índice necesario del elemento en la matriz del tema seleccionado actualmente.


Opacidad

A continuación, le daremos al usuario la capacidad de controlar la opacidad de las barras dibujadas. Configuración este es un proceso de dos pasos.


Sin transparencia
Con un valor de 0,8.

Añadiéndolo a las Opciones

 var defaults = // Other defaults here barOpacity: 0.8,;

Añadimos un barOpacidad Opción para los valores predeterminados, que permite al usuario cambiar la opacidad de los gráficos a un valor de 0 a 1, donde 0 es completamente transparente y 1 es completamente opaco..

Configurando el globalAlpha

 función drawGraph () for (index = 0; index 

los globalAlpha La propiedad controla la opacidad o transparencia del elemento representado. Establecemos el valor de esta propiedad al valor pasado o al valor predeterminado para agregar un poco de transparencia. Como valor predeterminado razonable, usamos un valor de 0.8 para que sea un poco transparente.


Cuadrícula

Una cuadrícula puede ser extremadamente útil para procesar los datos presentados en un gráfico. Aunque inicialmente quería una cuadrícula adecuada, luego me conformé con una serie de líneas horizontales alineadas con las etiquetas del eje Y y descarté por completo las líneas verticales, ya que solo se interponían en el camino de los datos. Con eso fuera de lugar, vamos a implementar una manera de renderizarlo.


Con rejilla desactivada
Con rejilla habilitada

Creando las líneas utilizando caminos y la lineTo El método parecía ser la solución más obvia para dibujar los gráficos, pero me encontré con un error de renderizado que hizo que este enfoque fuera inadecuado. Por eso me quedo con el fillRect Método para crear estas líneas también. Aquí está la función en su totalidad..

 función drawGrid () para (índice = 0; índice 

Esto es muy similar a dibujar las etiquetas del eje Y, excepto que en lugar de representar una etiqueta, dibujamos una línea horizontal que abarca el ancho del gráfico con un ancho de 1 px. los y La función nos ayuda en el posicionamiento..

Añadiéndolo a las Opciones

 var defaults = // Otros valores predeterminados aquí disableGrid: false,;

Añadimos un disableGrid opción a los valores predeterminados, lo que permite al usuario controlar si una cuadrícula se representa o no. Por defecto, se renderiza..

 // La función llama a (! Option.disableGrid) drawGrid (); 

Simplemente verificamos si el usuario desea que la cuadrícula se represente y procederemos en consecuencia.


Contornos

Ahora que todas las barras están coloreadas, carece de acento contra un fondo más claro. Para rectificar esto, necesitamos un golpe de 1px. Hay dos maneras de hacer esto. La primera, y la más fácil, sería simplemente agregar un strokeRect método para el dibujo gráfico método; O, podríamos usar el lineTo Método para trazar rápidamente los rectángulos. Elegí la ruta anterior desde antes lineTo método lanzó un error de representación extraño a mí.


Sin caricias
Con caricias

Añadiéndolo a Opciones

Primero lo agregamos a la por defecto objeto para dar al usuario el control de si esto se aplica o no.

 var defaults = // Otros valores predeterminados aquí showOutline: true,;
 function drawGraph () // Código anterior if (option.showOutline) ctx.fillStyle = "# 000"; ctx.strokeRect (x (índice), y (gValues ​​[índice]), ancho (), altura (gValores [índice]));  // Resto del código

Verificamos si el usuario desea representar los esquemas y, en caso afirmativo, continuamos. Esto es casi lo mismo que representar las barras reales, excepto que, en lugar de usar el fillRect método utilizamos el strokeRect método.


Sombreado

En la implementación original, no hay diferenciación entre el propio elemento del lienzo y el espacio de representación real de las barras. Vamos a rectificar esto ahora.


Sin sombra
Con sombreado
 function shadeGraphArea () ctx.fillStyle = "# F2F2F2"; ctx.fillRect (option.xOffset, 0, gWidth-option.xOffset, gHeight); 

Esta es una pequeña función que sombrea el área requerida. Cubrimos el elemento del lienzo menos el área cubierta por las etiquetas de ambos ejes. Los dos primeros parámetros apuntan a las coordenadas x e y del punto de inicio, y los dos últimos apuntan a la anchura y altura requeridas. Comenzando en option.offset, eliminamos el área cubierta por las etiquetas del eje Y, y limitando la altura a gHeight, Eliminamos las etiquetas del eje X.


Añadiendo características

Ahora que nuestro gráfico se ve bastante bonito, podemos concentrarnos en agregar algunas características nuevas a nuestro complemento. Miraremos cada uno por separado.

Considera este gráfico de los famosos picos de 8K..

Cuando el valor más alto es suficientemente alto, y la mayoría de los valores se encuentran dentro del 10% del valor máximo, el gráfico deja de ser útil. Tenemos dos formas de rectificar esto..


ShowValue

Vamos a empezar con la solución más fácil primero. Al representar el valor de los gráficos respectivos en la parte superior, el problema se resuelve virtualmente ya que los valores individuales se pueden diferenciar fácilmente. Así es como se implementa..

 var defaults = // Otros valores por defecto aquí showValue: true,;

Primero agregamos una entrada a la por defecto objeto para permitir al usuario encenderlo y apagarlo a voluntad.

 // La función llama a if (option.showValue) drawValue (); 

Verificamos si el usuario desea que se muestre el valor y procedemos en consecuencia.

 función drawValue () for (index = 0; index 

Recorremos el gValores Array y renderiza cada valor individualmente. Los cálculos que involucran valAsString y valX No son más que pequeños cálculos que nos ayudan en las sangrías correctas, por lo que no se ve fuera de lugar..


Escala

Esta es la más difícil de las dos soluciones. En este método, en lugar de iniciar las etiquetas del eje Y en 0, comenzamos mucho más cerca del valor mínimo. Te lo explicaré a medida que avanzamos. Tenga en cuenta que, en el ejemplo anterior, la diferencia entre los valores subsiguientes con respecto al valor máximo es bastante insignificante y no muestra tanta efectividad. Otros conjuntos de datos deberían facilitar el análisis de resultados..

Añadiéndolo a Opciones

 var defaults = // Otros valores predeterminados aquí escala: false;

Actualización de la función de escala

Desde el escala La función es una parte integral del proceso de representación, necesitamos actualizarla para permitir la función de escalado. Lo actualizamos así:

 escala de funciones (param) return ((option.scale)? Math.round (((param-minVal) / (maxVal-minVal)) * gHeight): Math.round ((param / maxVal) * gHeight)); 

Sé que esto parece un poco complicado, pero se ve de esa manera solo debido al uso del operador condicional ternario. Esencialmente, comprobamos el valor de opción.escala y si dice falso, se ejecuta el código más antiguo. Si es verdadero, en lugar de normalizar el valor como una función del valor máximo en la matriz, ahora lo normalizamos para que sea una función de la diferencia entre los valores máximo y mínimo. Lo que nos lleva a:

Actualizando el valores máximos Función

Ahora debemos averiguar tanto el valor máximo como el mínimo, y no solo el máximo que teníamos antes. La función se actualiza a esto:

 función minmaxValues ​​(arr) maxVal = 0; para (i = 0; iparseInt (arr [i])) minVal = parseInt (arr [i]);  maxVal * = 1.1; minVal = minVal - Math.round ((maxVal / 10)); 

Estoy seguro de que podrías lograr lo mismo en un solo bucle sin utilizar tantas líneas de código como yo, pero en ese momento me sentía particularmente poco creativo, así que ten paciencia. Con las formalidades de cálculo fuera del camino, emitimos un aumento del 5% al maxVal variable y al minVal variable, restamos un valor igual al 5% de maxVal's valor. Esto es para garantizar que las barras no se toquen en la parte superior cada vez y que las diferencias entre las etiquetas de cada eje Y sean uniformes.

Actualizando el dibujar etiquetas Función

Con todo el trabajo hecho, ahora procedemos a actualizar la rutina de representación de etiquetas del eje Y para reflejar la escala.

 función drawYlabels () ctx.save (); para (índice = 0; índice 

Actualización bastante carnosa si me preguntas! El núcleo de la función sigue siendo el mismo. Simplemente verificamos si el usuario ha habilitado el escalado y bifurcamos el código según sea necesario. Si está habilitado, alteramos la forma en que se asignan las etiquetas Y para asegurarnos de que se adhieran al nuevo algoritmo. En lugar de dividir el valor máximo en un número n de números espaciados uniformemente, ahora calculamos la diferencia entre el valor máximo y el valor mínimo, lo dividimos en números espaciados uniformemente y lo agregamos al valor mínimo para construir nuestra matriz de etiquetas del eje Y. Después de esto, procedemos normalmente, renderizando cada etiqueta individualmente. Ya que representamos el 0 más bajo manualmente, tenemos que verificar si la escala está habilitada y luego mostrar el valor mínimo en su lugar. No importa las pequeñas adiciones numéricas a cada parámetro pasado; es solo para asegurarse de que cada elemento del gráfico se alinee como se espera.


Redimensionamiento dinámico

En nuestra implementación anterior, codificamos las dimensiones del gráfico, lo que presenta una dificultad significativa cuando cambia el número de valores. Vamos a rectificar esto ahora..

Añadiéndolo a las Opciones

 var defaults = // Otros valores por defecto aquí cvHeight: 250, // In px;

Dejamos que el usuario establezca la altura del elemento de lienzo solo. Todos los demás valores se calculan dinámicamente y se aplican según sea necesario.

Actualizando el initCanvas Función

los initCanvas La función maneja toda la inicialización del lienzo y, por lo tanto, debe actualizarse para implementar la nueva funcionalidad..

 function initCanvas () $ ("#" + dataSource) .after (" "); // Intente acceder al elemento del lienzo cv = $ (" # bargraph - "+ dataSource) .get (0); cv.width = gValues.length * (option.barSpacing + option.barWidth) + option.xOffset + option.barSpacing; cv.height = option.cvHeight; gWidth = cv.width; gHeight = option.cvHeight-20; if (! cv.getContext) return; // Intenta obtener un contexto 2D para el lienzo y arroje un error si no puede ctx = cv.getContext ('2d'); if (! ctx) return;

Después de inyectar el elemento canvas, obtenemos una referencia al elemento creado. El ancho del elemento del lienzo se calcula en función del número de elementos en la matriz - gValores , el espacio entre cada barra - option.barSpacing, el ancho de cada barra - option.barWidth y finalmente option.xOffset. El ancho del gráfico cambia dinámicamente según cada uno de estos parámetros. La altura es modificable por el usuario y por defecto es de 220 px, con el área de renderizado de la barra en sí de 220 px. El 20px se asigna a las etiquetas del eje X.


Ocultar la fuente

Tiene sentido que el usuario quiera ocultar la tabla de origen una vez que se haya creado el gráfico. Teniendo esto en cuenta, permitimos que el usuario decida si desea eliminar la tabla o no.

 var defaults = // Otros valores predeterminados aquí hideDataSource: true,;
 if (option.hideDataSource) $ ("#" + dataSource) .remove ();

Verificamos si el usuario desea ocultar la tabla y, en caso afirmativo, la eliminamos completamente del DOM utilizando jQuery's retirar método.


Optimizando nuestro Código

Ahora que se ha hecho todo el trabajo duro, podemos revisar cómo optimizar nuestro código. Dado que este código se ha escrito en su totalidad con fines didácticos, la mayor parte del trabajo se ha resumido como funciones independientes y, además, son mucho más detallados de lo que necesitan ser.

Si realmente desea el código más simple posible, nuestro complemento completo, excluyendo la inicialización y el cálculo, puede reescribirse dentro de dos bucles. Un bucle a través de la gValores matriz para dibujar las propias barras y las etiquetas del eje X; y el segundo bucle iterando de 0 a NumYlabels para renderizar la grilla y las etiquetas del eje Y. El código se vería mucho más desordenado, sin embargo, debería llevar a una base de código significativamente más pequeña.


Resumen

Eso es todo amigos! Hemos creado un plugin de alto nivel completamente desde cero. Vimos una serie de temas en esta serie, incluyendo:

  • Mirando el esquema de representación del elemento canvas.
  • Algunos de los métodos de representación del elemento canvas..
  • La normalización de valores nos permite expresarlo en función de otro valor..
  • Algunas técnicas útiles de extracción de datos utilizando jQuery.
  • La lógica central de renderizar el gráfico..
  • Convertir nuestro script en un complemento de jQuery completo.
  • Cómo mejorarlo visualmente y extenderlo aún más en cuanto a características.

Espero que te hayas divertido tanto leyendo esto como lo había escrito yo. Siendo este un trabajo de línea de más de 270, estoy seguro de que omití algo. Siéntete libre de golpear los comentarios y preguntarme. O me criticas. O alabarme. Ya sabes, es tu llamada! Feliz codificacion!