Compartir a través de


Creación de aplicaciones HTML5

Cómo usar Canvas de HTML5 para visualizar datos

Brandon Satrom

Descargar el ejemplo de código

En los albores de Internet, cuando la red era poco más que una colección de texto estático con vínculos, existía un interés cada vez mayor en la compatibilidad con otros tipos de contenidos. En 1993, Marc Andreessen, creador del explorador Mosaic, el cual posteriormente evolucionaría y maduraría como Netscape Navigator, propuso la etiqueta IMG como un estándar para incrustar imágenes incorporadas en el texto de una página. Poco tiempo después la etiqueta IMG se convirtió en el estándar de facto para agregar recursos gráficos a las páginas web, un estándar que sigue siendo vigente. Incluso podríamos decir que, a medida que se hizo la transición de una web de documentos a una de aplicaciones, la etiqueta IMG adquirió más importancia que nunca.

Los medios en general se han vuelto, sin duda, más importantes de lo que nunca lo habían sido, y si bien la necesidad de medios en la web ha evolucionado en los últimos 18 años, la imagen ha permanecido estática. Los autores de contenidos para la web han intentado progresivamente usar medios dinámicos como audio, vídeo y animaciones interactivas en sus sitios y aplicaciones, y hasta hoy la principal solución fue un complemento como Flash o Silverlight.

Ahora, con HTML5, finalmente nos podemos deshacer del elemento media en el explorador. Probablemente ya haya oído hablar acerca de las nuevas etiquetas Audio y Video, que permiten que estos tipos de contenidos funcionen en el explorador como ciudadanos de primera clase, sin requerir de complemento alguno. El artículo del mes que viene abordará estos elementos y sus API en profundidad. También es posible que haya oído hablar acerca del elemento canvas, una superficie de dibujo con un conjunto rico de API para JavaScript que permite crear y manipular imágenes y aplicaciones sobre la marcha. Canvas tiene el potencial de ser para los contenidos dinámicos y programables mediante scripts lo mismo que IMG fue para los contenidos gráficos estáticos.

Pero por muy estimulante que sea el elemento canvas, padece de cierto problema de percepción. Debido al enorme poder de este elemento, canvas generalmente se ejemplifica con animaciones y juegos complejos. Y si bien estos ilustran su potencial, también son conducentes a generar la idea de que trabajar con el lienzo es complicado y difícil, y que solo debe intentarse en casos complejos, como en animaciones o en juegos.

En el artículo de este mes, me gustaría abstraerme de toda la pompa y la complejidad de canvas, y mostrar algunos de sus usos simples y básicos, todo con el fin de posicionar al lienzo como una herramienta poderosa de visualización de datos en las aplicaciones web. Con eso en mente, me centraré en la introducción a canvas y cómo dibujar líneas, formas y texto sencillos. Después, analizaré cómo puede usar degradados en las formas, y cómo agregar imágenes externas a canvas. Para terminar, y como lo he hecho a lo largo de esta serie, concluiré con un breve análisis sobre el uso de polyfills en canvas para brindar compatibilidad con los exploradores más antiguos.

Introducción al elemento canvas de HTML5

Según las especificaciones de HTML5 de W3C (w3.org/TR/html5/the-canvas-element.html), el elemento canvas “proporciona a los scripts un mapa de bits dependiente de la resolución que funciona como lienzo, el cual puede usarse para representar grafos, los gráficos de juegos u otras imágenes visuales sobre la marcha”. En realidad, canvas se define en dos especificaciones del W3C. La primera forma parte de la especificación central de HTML5, donde se define minuciosamente el elemento propiamente tal. Esta especificación describe cómo usar el elemento canvas, cómo obtener su contexto de dibujo, las API para exportar el contenido de canvas y diversos factores relativos a la seguridad para los proveedores de exploradores. La segunda es el contexto 2D de canvas de HTML (w3.org/TR/2dcontext), el cual abordaremos en seguida.

Empezar a usar canvas es tan simple como agregar un elemento <canvas> al marcado de HTML5, de esta manera:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <title>My Canvas Demo </title>               
    <link rel="stylesheet" href="style.css" />
  </head>
  <body>
    <canvas id="chart" width="600" height="450"></canvas>       
  </body>
</html>

Aunque ahora contamos con un eleme­­nto canvas en el DOM, al poner este marcado en la página no ocurre nada, puesto que el elemento canvas no tiene ningún contenido hasta que lo agreguemos. Aquí es donde entra en juego el contexto de dibujo. Para ver donde se ubica el lienzo en blanco, podemos usar CSS y darle un estilo; agregaré una línea azul punteada alrededor del elemento en blanco.

canvas {
    border-width: 5px;
    border-style: dashed;
    border-color: rgba(20, 126, 239, 0.50)
}

En la Figura 1 vemos el resultado en Internet Explorer 9 o superior, Chrome, Firefox, Opera o Safari.

A Blank, Styled Canvas Element
Figura 1 Un elemento canvas en blanco y con estilo

Al usar canvas, haremos la mayor parte del trabajo en JavaScript, donde se pueden usar las API expuestas de un contexto de dibujo del canvas para manipular cada uno de los píxeles de la superficie. Para obtener el contexto de dibujo del canvas, deberemos obtener el elemento canvas del DOM y después llamar al método getContext de ese elemento.

var _canvas = document.getElementById('chart');
var _ctx = _canvas.getContext("2d");

GetContext devuelve un objeto con una API que permite dibujar en ese canvas. El primer argumento de ese método (en este caso “2d”) especifica la API de dibujo que queremos usar para el canvas. La cadena “2d” hace referencia al contexto 2D del canvas de HTML que mencioné anteriormente. Como podrá adivinar, 2D significa que se trata de un contexto de dibujo de dos dimensiones. A la hora de escribir este artículo, el contexto 2D es el único contexto de dibujo que se ha adoptado ampliamente y es el que usaremos en este documento. Actualmente se está realizando trabajo y experimentación sobre el contexto de dibujo en 3D, de manera que en el futuro canvas nos permitirá escribir aplicaciones más poderosas.

Dibujo de líneas, formas y texto

Ahora que contamos con un elemento canvas en nuestra página y que obtuvimos su contexto de dibujo en JavaScript, podemos empezar a agregar contenido. Dado que quiero enfocarme en la visualización de datos, usaré canvas para dibujar un gráfico de barras para representar los datos de ventas del mes actual para una tienda de artículos deportivos ficticia. Para este ejercicio deberemos dibujar las líneas de los ejes, las formas y el relleno de las barras, y el texto de las etiquetas en cada eje y barra.

Comencemos con las líneas de los ejes x e y. Dibujar líneas (o rutas) con el contexto del canvas es un proceso de dos pasos. Primero, debemos “trazar” las líneas en la superficie mediante una serie de llamados a lineTo (x, y) y moveTo (x, y). Cada método toma las coordenadas x e y del objeto canvas (el cual empieza en la esquina superior izquierda) para usarlas al realizar la operación (en oposición a las coordenadas de la pantalla). El método moveTo nos desplazará a las coordenadas que especifiquemos y lineTo trazará una línea desde las coordenadas actuales a las que especifiquemos. Por ejemplo, el siguiente código trazará nuestro eje y en la superficie:

// Draw y axis.
_ctx.moveTo(110, 5);
_ctx.lineTo(110, 375);

Si agrega este código al script y lo ejecuta en el explorador, verá que no ocurre nada. Dado que el primer paso consiste solamente en un trazado, no aparece ningún dibujo en la pantalla. El trazado nada más instruye al explorador que tome nota de una operación de ruta que más adelante aparecerá en pantalla. Cuando estemos listo para dibujar rutas en la pantalla, opcionalmente podemos configurar la propiedad strokeStyle del contexto y después llamar al método stroke, el cual llenará las líneas invisibles. El resultado está representado en la Figura 2.

 

// Define Style and stroke lines.
_ctx.strokeStyle = "#000";
_ctx.stroke();

A Single Line on the CanvasFigura 2 Una línea en el lienzo

Dado que las líneas definitorias (lineTo y moveTo) y las líneas que dibujan (stroke) están desacopladas, podemos generar una serie de operaciones lineTo y moveTo en un lote y después dibujarlas en pantalla todas a la vez. Además de los ejes x e y, también lo haré con las operaciones que dibujan las flechas en cada eje. La función completa para dibujar los ejes se muestra en la Figura 3 y el resultado en la
Figura 4.

Figura 3 La función drawAxes

function drawAxes(baseX, baseY, chartWidth) {
   var leftY, rightX;
   leftY = 5;
   rightX = baseX + chartWidth;
   // Draw y axis.
   _ctx.moveTo(baseX, leftY);
   _ctx.lineTo(baseX, baseY);
   // Draw arrow for y axis.
   _ctx.moveTo(baseX, leftY);
   _ctx.lineTo(baseX + 5, leftY + 5);
   _ctx.moveTo(baseX, leftY);
   _ctx.lineTo(baseX - 5, leftY + 5);
   // Draw x axis.
   _ctx.moveTo(baseX, baseY);
   _ctx.lineTo(rightX, baseY);
   // Draw arrow for x axis.
   _ctx.moveTo(rightX, baseY);
   _ctx.lineTo(rightX - 5, baseY + 5);
   _ctx.moveTo(rightX, baseY);
   _ctx.lineTo(rightX - 5, baseY - 5);
   // Define style and stroke lines.
   _ctx.strokeStyle = "#000";
   _ctx.stroke();
}

Completed X- and Y-Axes
Figura 4 Ejes x e y terminados

Ahora ya tenemos nuestros ejes, pero probablemente sea una buena idea etiquetarlos para que sean más útiles. El contexto canvas 2D ofrece algunas API para agregar texto a los elementos canvas, así que no tendremos que improvisar soluciones para crear elementos como texto flotante sobre el lienzo, por ejemplo. Dicho esto, vale mencionar que el texto de canvas no proporciona un modelo de cuadro ni acepta estilos CSS definidos para elementos de texto que abarcan todo el ancho de la página, etc. La API proporciona un atributo llamado font que funciona igual que la regla font en CSS (también ofrece las propiedades textAlign y textBaseline para controlar en cierta medida la posición relativa de las coordenadas proporcionadas) pero aparte de esto, para dibujar texto en el canvas basta con escoger el punto exacto del lienzo donde queramos colocar el texto que proporcionamos.

Como el eje x representa los productos en nuestra tienda de artículos deportivos ficticia tendremos que etiquetar este eje en consecuencia:

var height, widthOffset;
height = _ctx.canvas.height;
widthOffset = _ctx.canvas.width/2;
_ctx.font = "bold 18px sans-serif";
_ctx.fillText("Product", widthOffset, height - 20);

En este fragmento de código configuramos la propiedad de fuente opcional y proporcionamos una cadena para dibujarla en la superficie, junto con las coordenadas x e y para usarlas como puntos de partida de la cadena. En este ejemplo, dibujaré la palabra “Product” en el centro del canvas, 20 píxeles sobre la parte inferior, de manera que quede espacio para las etiquetas con los productos en el gráfico de barras. Haré algo similar para la etiqueta del eje y, el cual contiene los datos de ventas de cada producto. En la Figura 5 vemos el resultado.

Canvas with Text
Figura 5 Lienzo con texto

Ahora que ya tenemos un marco para nuestro gráfico podemos agregar las barras. Creemos algunos datos de ventas imaginarios para el gráfico de barras; los definiré en JavaScript como una matriz de literales de objeto.

var salesData = [{
   category: "Basketballs",
   sales: 150
}, {
   category: "Baseballs",
   sales: 125
}, {
   category: "Footballs",
   sales: 300
}];

Con estos datos a mano, podemos usar fillRect y fillStyle para dibujar las barras en el gráfico.

La función fillRect (x, y, width, height) dibujará un rectángulo en el lienzo en las coordenadas x e y, con el alto y ancho que especifiquemos. Es importante observar que fillRect dibuja las formas a partir del extremo superior derecho, radiando hacia afuera, a no ser que especifiquemos valores negativos para el alto o el ancho, en cuyo caso el relleno se radiará en la dirección opuesta. En el caso de las tareas de dibujo como la creación de gráficos, esto implica que se dibujarán las barras desde arriba hacia abajo y no de abajo hacia arriba.

Para dibujar las barras, podemos iterar a través de la matriz con los datos de venta y llamar la función fillRect con las coordenadas pertinentes:

var i, length, category, sales;
var barWidth = 80;
var xPos = baseX + 30;
var baseY = 375;       
for (i = 0, length = salesData.length; i < length; i++) {
   category = salesData[i].category;
   sales = salesData[i].sales;
   _ctx.fillRect(xPos, baseY - sales-1, barWidth, sales);
   xPos += 125;
}

En este código, el ancho de cada barra es un valor fijo, mientras que el alto se obtiene de la propiedad sales de cada producto dentro de la matriz. En la Figura 6 podemos ver el resultado de este código.

Rectangles as Bar Chart Data
Figura 6 Rectángulos para los datos del gráfico de barras

Ahora tenemos un gráfico que, si bien desde el punto de vista técnico es exacto, tiene unas barras negras que dejan mucho que desear. Vamos a acicalarlas con algo de color y luego agregaremos un efecto de degradado.

Trabajar con colores y degradados

Al llamar al método fillRect de un contexto del dibujo, el contexto usa el valor actual de la propiedad fillStyle para aplicar un estilo al rectángulo durante el proceso de dibujo. El estilo predeterminado es negro sólido, razón por la cual nuestro gráfico se ve como el que observamos en la Figura 6. El método fillStyle acepta colores con nombre, hexadecimales y RGB, así que agreguemos un poco de funcionalidad para darle estilo a las barras antes de dibujarlas.

// Colors can be named hex or RGB.
colors = ["orange", "#0092bf", "rgba(240, 101, 41, 0.90)"];       
...
_ctx.fillStyle = colors[i % length];
_ctx.fillRect(xPos, baseY - sales-1, barWidth, sales);

Primero crearemos una matriz de colores. Después, a medida que iteramos por cada producto del bucle, usaremos uno de esos colores como el relleno del elemento. En la figura 7 vemos el resultado.

Using fillStyle to Style Shapes
Figura 7 Aplicación de estilo a las formas con fillStyle

Esto ya se ve mejor, pero fillStyle es muy flexible y permite aplicar degradados lineales y radiales en lugar de los colores sólidos. El contexto de dibujo de 2D especifica dos funciones para crear degradados, createLinearGradient y createRadialGradient, que mejoran el estilo de las formas mediante transiciones suaves de colores.

Para este ejemplo, definiré una función createGradient que aceptará las coordenadas x e y para el degradado, un ancho y el color primario:

function createGradient(x, y, width, color) {
   var gradient;
   gradient = _ctx.createLinearGradient(x, y, x+width, y);
   gradient.addColorStop(0, color);
   gradient.addColorStop(1, "#efe3e3");
   return gradient;
}

Después de llamar a createLinearGradient con mis coor­denadas de inicio y fin, agregaré dos delimitadores de color al objeto de gradiente que devuelve el contexto del dibujo. El método addColorStop agrega transiciones de color junto con el degradado, podemos llamarlo todas las veces que queramos con los primeros valores entre 0 y 1 para el primer parámetro. Una vez que hayamos configurado el degradado, lo devolvemos de la función.

Luego podemos establecer el objeto de gradiente como la propiedad fillStyle en el contexto, en lugar de las cadenas hexadecimales y RGB que especifiqué en el ejemplo anterior. Usaré los mismos colores como punto de partida y después los difuminaré en un gris claro.

colors = ["orange", "#0092bf", "rgba(240, 101, 41, 0.90)"];
_ctx.fillStyle = createGradient(xPos, baseY - sales-1, barWidth, colors[i % length]);
_ctx.fillRect(xPos, baseY - sales-1, barWidth, sales);

En la Figura 8 podemos ver los resultados de la opción de gradiente.

Using Gradients in a Canvas
Figura 8 Uso de gradientes en el lienzo

Trabajo con imágenes

Llegados a este punto, ya tenemos un gráfico bastante atractivo, y pudimos representarlo en el explorador con unas pocas docenas de líneas de JavaScript. Podría detenerme aquí, pero aun queda una API básica de canvas relacionada con el trabajo con imágenes con la que quiero trabajar. Canvas no solo permite reemplazar las imágenes estáticas con contenidos interactivos y creados mediante scripts, sino que también usa imágenes estáticas para mejorar las visualizaciones en el lienzo.

En esta demostración quisiera usar imágenes en las barras del gráfico de barras. Y no cualquier imagen, sino que imágenes de los artículos mismos. Con esta meta en mente, en mi sitio web existe una carpeta que contiene imágenes JPG de todos los productos, en este caso basketballs.jpg, baseballs.jpg y footballs.jpg. Todo lo que tengo que hacer es ubicar cada imagen y establecer su tamaño adecuadamente.

El contexto de dibujo en 2D define un método drawImage con tres sobrecargas, las que aceptan tres, cinco o nueve parámetros. El primer parámetro siempre es el elemento DOM que vamos a dibujar. La versión más simple de drawImage también acepta las coordenadas x e y del lienzo y dibuja la imagen tal cual es en ese lugar. También podemos proporcionar el alto y ancho como los dos últimos parámetros, lo que escala la imagen antes de dibujarla en la superficie. Para terminar, el uso más complejo de drawImage nos permite recortar una imagen para ajustarla a un rectángulo determinado, escalarla a un conjunto determinado de dimensiones y, finalmente, dibujarla en el canvas en las coordenadas especificadas.

Como las imágenes de origen que tengo son imágenes de gran escala que sirvieron en otras partes del sitio web, usaré el segundo método. En este ejemplo, en vez de llamar al método fillRect en cada elemento al iterar por los elementos de la matriz salesData, crearé un elemento DOM de imagen, estableceré la fuente en una de las imágenes de los productos, y representaré una versión recortada de esa imagen en mi gráfico, tal como se indica en la Figura 9.

Figura 9 Dibujar imágenes en un canvas

// Set outside of my loop.
xPos = 110 + 30;     
// Create an image DOM element.
img = new Image();
img.onload = (function(height, base, currentImage, currentCategory) {
  return function() {
    var yPos, barWidth, xPos;
    barWidth = 80;
      yPos = base - height - 1;
    _ctx.drawImage(currentImage, 30, 30, barWidth, height, xPos, yPos,
      barWidth, height);
      xPos += 125;           
  }
})(salesData[i].sales, baseY, img, salesData[i].category);
img.src = "images/" + salesData[i].category + ".jpg";

Como estoy creando estas imágenes en forma dinámica, en vez de agregarlas manualmente en mi marcado durante el diseño, no puedo pensar que podré configurar el origen de la imagen para luego dibujar esa imagen en el lienzo. Para asegurarme de que cada imagen se dibuja solo en una vez que se haya cargado por completo, agregaré la lógica de dibujo al evento onload de la imagen y después encapsularé ese código en una función que se invoque a sí misma. Esto crea una clausura con unas variables que apuntan a la categoría de producto, las variables de ventas y posicionamiento correctas. Podemos ver el resultado en la Figura 10.

Using Images on a Canvas
Figura 10 El uso de imágenes en un canvas

Uso de un polyfill para canvas

Como ya ha de saber, las versiones anteriores a Internet Explorer 9 y las versiones más antiguas de otros productos no son compatibles con el elemento canvas. Para verlo por su cuenta, puede al abrir el proyecto de ejemplo en Internet Explorer y presionar F12 para abrir las herramientas para desarrolladores. En las herramientas de F12, puede cambiar el Modo del explorador a Internet Explorer 8 o a Internet Explorer 7 y actualizar la página. Lo más probable es que aparezca una excepción de JavaScript con el mensaje “El objeto no es compatible con la propiedad del método getContext”. El contexto de dibujo 2D no está disponible, ni tampoco el elemento canvas mismo. También es importante saber que, incluso en Internet Explorer 9, canvas no está disponible, a menos que se especifique un DOCTYPE. Como mencioné en el primer artículo de esta seriemsdn.microsoft.com/magazine/hh335062), siempre es recomendable usar <!DOCTYPE html> en la primera línea de todas las páginas HTML para asegurarnos de que estén disponibles las características más recientes del explorador.

El curso de acción más simple que podemos tomar para los usuarios con exploradores incompatibles con canvas es usar elementos de reserva tales como imágenes o texto. Por ejemplo, para mostrar una imagen de reserva a los usuarios, podemos usar el siguiente marcado:

<canvas id=”chart”>
  <img id=”chartIMG” src=”images/fallback.png”/>
</canvas>

Cualquier contenido que ubiquemos dentro de la etiqueta <canvas>, se mostrará a los usuarios solo si el explorador no es compatible con canvas. Esto significa que podemos colocar imágenes o texto dentro del canvas como un elemento de reserva simple y sin comprobaciones para los usuarios.

Lo bueno es que si queremos extender la compatibilidad de reserva, existe toda una gama de soluciones polyfill para canvas, así que podemos usarlos confiadamente con los exploradores más antiguos, siempre y cuando estudiemos las soluciones potenciales y nos mantengamos al tanto de las limitaciones de un polyfill dado. Tal como expliqué en otros artículos de la serie, el punto de partida para encontrar un polyfill para cualquier tecnología de HTML5 debiera ser siempre la página de polyfills para diferentes exploradores de HTML5, que se encuentra en la wiki de Modernizr en GitHub (bit.ly/nZW85d). A la hora de escribir este artículo existen hay varios polyfills de canvas disponibles, dos de los cuales recurren a soluciones de reserva con Flash y Silverlight.

 En el proyecto de ejemplo descargable de este artículo, usé explorercanvas (code.google.com/p/explorercanvas), el cual usa el Lenguaje de marcado de vectores (VML), compatible con Internet Explorer, para crear aproximaciones cercanas a la funcionalidad de canvas, y canvas-text (code.google.com/p/canvas-text), el cual agrega funcionalidades adicionales para representar texto en los exploradores más antiguos.

Tal como describí en los artículos anteriores, puede usar Modernizr para dar compatibilidad a la detección de características para canvas (y canvastext) en un explorador al llamar a Modernizr.canvas y luego usar Modernizr.load para cargar explorercanvas en forma asincrónica cuando sea necesario. Para obtener más información, consulte modernizr.com.

Si no desea usar Modernizr, existe otra forma de agregar a explorercanvas en forma condicional a las versiones más antiguas de Internet Explorer: los comentarios condicionales:

<!--[if lt IE 9]>
  <script src="js/excanvas.js"></script>
  <script src="js/canvas.text.js"></script>
<![endif]-->

Cuando Internet Explorer 8 o sus versiones anteriores se encuentran con un comentario con este formato, ejecutan el bloque como una declaración if e incluyen los archivos de script explorercanvas y canvas-text. Otros exploradores, incluyendo a Internet Explorer 10, tratan al bloque entero como comentario y lo omiten por completo.

Al evaluar un posible polyfill para su aplicación, asegúrese de ver cuál es el grado de compatibilidad que este tiene con el contexto de dibujo 2D. Pocos de ellos entregan una compatibilidad completa para todos los usos, aunque casi todos pueden controlar los casos básicos que vimos en este artículo.

Aunque no puedo abarcar todo aquí, existen muchas más cosas que puede hacer con canvas, desde responder a eventos de clic (y a otros) y cambiar los datos de canvas hasta animar la superficie de dibujo, representar y manipular imágenes píxel por píxel, guardar estados y exportar toda la superficie como una imagen. De hecho, hay libros dedicados completamente a canvas. No necesita ser un desarrollador de juegos para experimentar con el poder de canvas; espero haberlo convencido de esto durante la revisión de los aspectos elementales que vimos en este artículo. Lo invito a leer las especificaciones por su cuenta y a aventurarse en esta nueva y emocionante tecnología gráfica.

Si busca más información sobre la compatibilidad de canvas en Internet Explorer 9, consulte en línea la Guía para desarrolladores de IE9 (msdn.microsoft.com/ie/ff468705). Además, revise también los ejemplos de Canvas Pad disponibles en el sitio IE Test Drive (bit.ly/9v2zv5). Para obtener una lista de varios polyfills para canvas, compatibles con múltiples exploradores, revise la lista completa de polyfilling en (bit.ly/eBMoLW).

Para terminar, todos los ejemplos de este artículo (que se encuentran disponibles en línea) fueron creados con WebMatrix, una herramienta gratuita y ligera para el desarrollo de web de Microsoft. Puede probar WebMatrix en aka.ms/webm.

Brandon Satrom es un evangelizador desarrollador de Microsoft radicado en las afueras de Austin. Tiene un blog en userinexperience.com y lo puede seguir en Twitter, en twitter.com/BrandonSatrom.

Gracias a los siguientes expertos técnicos por su ayuda en la revisión de este artículo: Jatinder Mann y Clark Sell