Crear gráficos para visualización de datos con JavaScript y Echarts

Crear gráficos para visualización de datos con JavaScript y Echarts es clave para construir aplicaciones web de data science entre otros.

En este artículo te explico cómo implementar esta fantástica librería para crear impresionantes gráficos de todo tipo, como los del siguiente ejemplo. Haz click en la imagen para abrirlo en una ventana nueva.

Ejercicio implementando Echarts. Haz click en la imagen para abrirlo en una ventana nueva
Ejercicio implementando Echarts. Haz click en la imagen para abrirlo en una ventana nueva

Puedes saltarte la teoría y pasar directamente a la parte práctica del artículo, haz click aquí

La visualización de datos es un área del desarrollo fascinante.

Es satisfactorio lograr que, mediante gráficos interactivos, los usuarios puedan comprender de forma clara e intuitiva el contenido de un conjunto de datos.

Es más, si un proyecto de estas características se resuelve bien, no solo permite entender el contenido implícito en los datos, también ayuda a tomar mejores decisiones sobre estos.

El ejemplo más simple que ilustra este tipo de proyectos, aunque no por ello el menos útil, es el gráfico de barras.

Esta forma de representación de datos, permite al usuario poner en contexto múltiples valores para, posteriormente, extraer conclusiones.

Por supuesto, existen muchos otros gráficos. 

Cada uno con unas características específicas orientadas a facilitar la tarea de comprensión y lectura al usuario.

Por lo pronto se me ocurren algunos de los más utilizados.

  • Gráficos de líneas.
  • Gráficos de barras.
  • Gráficos de pastel o queso.
  • Gráficos de distribución de puntos.
  • Gráficos sobre formas geométricas o mapas.
  • Gráficos de velas japonesas.
  • Gráficos en forma de radar.
  • Gráficos de Sankey
  • Mapas calientes.
  • Embudos de conversión

Y un largo etcétera.

Pero aquí viene la gran pregunta, ¿cómo podemos crear programáticamente todos estos gráficos de forma fácil y con un acabado profesional?

Echarts la libreria JavaScript para crear todo tipo de gráficos y visualización de datos
Echarts la libreria JavaScript para crear todo tipo de gráficos y visualización de datos

Pues la respuesta viene dada de la mano de Echarts, una librería JavaScript orientada a la visualización de datos y a la creación de todo tipo de gráficos web interactivos.

Echarts, la librería JavaScript para la creación de gráficos dinámicos

A decir verdad, por aquí ya hemos visto alguna que otra biblioteca frontend para la creación de componentes web que organizan y representan datos.

Algunas de ellas fueron Frappé Gantt y AGGrid, que permitían generar diagramas de Gantt y tablas dinámicas respectivamente.

Crear diagramas de Gantt con HTML5

Crear tablas dinámicas con AgGrid

Sin embargo, estos recursos están pensados para generar componentes de un tipo muy concreto, pero son de poca ayuda si tu aplicación necesita una herramienta más versátil.

Echarts, por otro lado, es una opción excelente para construir casi cualquier tipo de visualización de datos.

Tal y como veremos más adelante, su API ofrece a los desarrolladores la posibilidad de renderizar elementos interactivos de forma totalmente declarativa.

Con unas pocas líneas de código, puedes convertir una serie de datos en uno de los gráficos antes listados.

Solo hace falta echar un vistazo a los ejemplos de la web oficial, para darse cuenta del enorme potencial de esta herramienta:

https://echarts.apache.org/examples/en/index.html

Echarts es un proyecto “open source” de licencia Apache 2.0, que pertenece a la “Apache software fundation” (ASF).

https://apache.org/

El repositorio en Github de este recurso está valorado con más de 58.5k estrellas y su paquete se descarga una media de 851.126 veces por semana a través de NPM.

La flexibilidad de Echarts permite crear más de 20 tipos de gráficos completamente customizables

Su motor de renderizado se puede configurar tanto para “canvas” como para “svg” para adaptarse a las necesidades de cada proyecto.

https://echarts.apache.org/handbook/en/best-practices/canvas-vs-svg

Además goza del soporte de una enorme comunidad de desarrolladores, que trabajan a diario en su mantenimiento y en la creación de nuevas mejoras.

Sin ir más lejos, gracias a este equipo, existen extensiones y “wrappers” que amplian las funcionalidades base de Echarts o dan soporte a frameworks como React, Vue o Angular.

¿Cómo crear gráficos con Echarts?

Vamos a ver cómo funciona la API de Echarts, y cuán compleja es su implementación.

Los primeros pasos a seguir para disponer de Echarts en tu entorno de desarrollo es instalar la librería mediante NPM.

npm install echarts

Tras su instalación, existen dos formas de importar el recurso. 

Cargado todas las funcionalidades, o haciendo una selección de sólo aquellas capacidades que deseamos en nuestra app.

Siempre es mejor limitar el número de recursos a cargar, así lograremos un fichero menos pesado en el momento de compilar.

Un ejemplo de cómo importar y registrar algunos componentes de Echarts sería el siguiente:

import * as echarts from 'echarts/core';
import { BarChart } from 'echarts/charts';
import {
  TitleComponent,
  TooltipComponent,
  GridComponent,
} from 'echarts/components';
import { CanvasRenderer } from 'echarts/renderers';
echarts.use([
  BarChart,
  TitleComponent,
  TooltipComponent,
  GridComponent,
  CanvasRenderer
]);

Tal y como se aprecia en el código, en la primera línea importamos solo el “core” de Echarts.

Seguidamente solicitamos los componentes necesarios y los integramos en la instancia “echarts” mediante el método “use”.

Con esto, ya estamos en disposición de pedirle a Echarts el gráfico que deseamos con los métodos “init” y “setOption”.

const myChart = echarts.init(document.getElementById('main'));
myChart.setOption({
  title: {
    text: 'ECharts Getting Started Example'
  },
  tooltip: {},
  xAxis: {
    data: ['shirt', 'cardigan', 'chiffon', 'pants', 'heels', 'socks']
  },
  yAxis: {},
  series: [
    {
      name: 'sales',
      type: 'bar',
      data: [5, 20, 36, 10, 10, 20]
    }
  ]
});

La función “init” necesita como primer parámetro una referencia a un elemento del DOM que actuará de contenedor para el gráfico.

Además del primer parámetro, init también admite dos parámetros más para especificar el tema que se desea usar y para definir opciones de configuración generales, como el tipo de renderizado (SVG o Canvas).

Por defecto, Echarts acomodará el gráfico al tamaño definido por CSS del contenedor.

A través del método setOption definimos la tipología y características del gráfico.

El objeto que se envía como parámetro al método “setOption” puede ser bastante complejo ya que en él se declaran todas las propiedades de los distintos gráficos a pintar.

Listar absolutamente todas las opciones que ofrece este objeto de configuración, sería demasiado para este post, así que he hecho una selección de lo que me parece más relevante:

  • Title: Con este atributo se puede agregar títulos a los gráficos. Entre las opciones de este objeto, podemos declarar, por supuesto, el texto, pero además su posición en el gráfico y los estilos tipográficos.
  • Legend: En muchos gráficos es habitual encontrar leyendas que amplíen o contextualicen la información. Con este parámetro podremos especificar su contenido y estilos.
  • Grid: Ideal para especificar las características de un eje de coordenadas X y Y. Solo para aquellos tipos de gráficos que se dibujan dentro de un grid
  • xAxis y yAxis: Como sus nombres indican, permiten configurar los ejes verticales y horizontales dentro de un grid.
  • Polar: Se puede usar para construir gráficos circulares que representan dispersión de puntos o líneas.
  • Radar: Objeto para definir las coordenadas de un gráfico de tipo radar.
  • DataZoom: Si deseas que el usuario pueda hacer zoom sobre los datos de uno de tus gráficos, a través de esta opción lo puedes lograr.
  • Tooltip: Al incluir esta clave acompañada de otro objeto JavaScript, Echarts va renderizar un elemento en la interfaz que se mostrará al interactuar con el gráfico. En este elemento flotante, puede aparecer información adicional.
  • Geo: Habilita el uso de gráficos con coordenadas geográficas.
  • Parallel: Ideal para representar datos complejos multi-dimensionales.
  • Timeline: Si se añade esta clave, Echarts incluirá una línea temporal interactiva al gráfico. Este recurso es genial para representar datos que evolucionan a lo largo de un período de tiempo.
  • Calendar: A través de calendar se pueden generar gráficos de colores dentro de un calendario de fechas. Seguro que te suena de haber visto algo parecido en Github.
  • Series: Sin duda la propiedad más importante, esta clave debe contener un array de objetos que describen el tipo de gráfico a generar y los datos que lo “alimentan”.

Te dejo el enlace a toda la documentación para que estudies en detalle cada parámetro.

https://echarts.apache.org/en/option.html#title

Debes tener en cuenta que algunas de las propiedades mencionadas sólo tienen sentido incluirlas si se desea “pintar” un determinado gráfico.

Por ejemplo, la propiedad “polar” no se debe especificar si solo queremos generar un gráfico de barras. 

También quiero destacar un poco más en detalle la propiedad “series”, ya que resulta imprescindible para entender cómo trabaja Echarts.

Un ejemplo de “series” podría es el siguiente:

myChart.setOption({
  //...
  series: [
    {
      name: 'sales',
      type: 'bar',
      data: [5, 20, 36, 10, 10, 20]
    }
  ]
});

Fíjate que se trata de un array donde cada objeto tiene una propiedad “type” que indica qué tipo de gráfico queremos representar, y otra propiedad “data” con los valores a representar.

Volviendo al objeto que Echarts ha instanciado, debes saber que “SetOption” es solo uno de muchos de sus métodos.

A continuación te listo algunos otros igual de relevantes:

  • getWidth() y getHeight(): Nos devolverán el tamaño del gráfico pintado.
  • getOption(): Retornará el objeto de configuración actualizado.
  • resize(): Al llamar a éste método, forzaremos que Echarts acomode de nuevo el gráfico al contenedor.
  • on() y off(): Son esenciales para agregar y quitar la escucha de eventos sobre cada gráfico. Si deseas añadir interactividad a tus gráficos definitivamente debes estudiar en detalle estas dos funciones.
  • dispose() y clear(): Si necesitas destruir o limpiar un gráfico puedes llamar a estos dos métodos respectivamente para hacerlo de forma óptima.

De nuevo te dejo un enlace a la documentación para ver el resto de métodos disponibles:

https://echarts.apache.org/en/api.html#echartsInstance

Tras este breve repaso a la API de Echarts, ha llegado el momento de poner en práctica lo aprendido.

Crear gráficos interactivos con JavaScript y Echarts

Vamos a construir una aplicación web donde el usuario podrá escoger entre tres personajes y seguidamente se le mostrarán datos en forma de gráficos animados.

Antes de empezar puedes acceder y descargar el código fuente de ésta práctica terminada, por si en algún momento del tutorial algo no se entiende del todo.

https://github.com/Danivalldo/libreriasjs/tree/master/Echarts

En este tutorial no detallo el proceso de preparación del entorno de desarrollo frontend. Si quieres conocer más sobre esto, te recomiendo el artículo en el que analizaba Vite.

Empezamos instalando la dependencia necesaria:

npm i echarts

Seguidamente preparamos un layout básico con HTML.

<div class="header">
  <h1>Elige a un luchador/a</h1>
  <p>Haz clic en el luchador/a que quieras para ver sus estadísticas</p>
</div>
<div class="main-contianer">
</div>
<div class="footer">
  <a href="https://libreriasjs.com">libreriasjs.com</a>
</div>

Definimos una cabecera con un título, una área principal y un pié de página.

En la área principal preparamos unos contenedores donde incluiremos los selectores de personajes, y una zona donde “pintaremos” 4 gráficos con Echarts.

<div class="characters"></div>
<div class="charts">
  <div>
    <h3>Puntuación en torneos</h3>
    <div class="chart pie-chart"></div>
  </div>
  <div>
    <h3>Habilidades</h3>
    <div class="chart radar-chart"></div>
  </div>
  <div class="">
    <h3>Calendario de victorias</h3>
    <div class="chart calendar-chart"></div>
  </div>
  <div>
    <h3>Estrategia de combate</h3>
    <div class="chart sankey-chart"></div>
  </div>
</div>

Para darle un poco de color a la interfaz he preparado una hoja de estilos CSS que puedes incluir en tu proyecto.

https://github.com/Danivalldo/libreriasjs/blob/master/Echarts/style.css

No dudes en modificarla para ajustar los acabados a tu gusto.

Finalmente pasamos a construir el comportamiento con JavaScript en “main.js” y en otros archivos dentro del directorio “scripts”.

Centrémonos de momento en el archivo “scripts/initEcharts.js”.

import * as echarts from "echarts/core";
import {
  BarChart,
  RadarChart,
  SankeyChart,
  PieChart,
  HeatmapChart,
} from "echarts/charts";
import {
  TitleComponent,
  TooltipComponent,
  LegendComponent,
  GridComponent,
  PolarComponent,
  CalendarComponent,
  VisualMapComponent,
} from "echarts/components";
import { CanvasRenderer } from "echarts/renderers";
echarts.use([
  BarChart,
  RadarChart,
  SankeyChart,
  HeatmapChart,
  PieChart,
  TitleComponent,
  TooltipComponent,
  LegendComponent,
  GridComponent,
  PolarComponent,
  CalendarComponent,
  VisualMapComponent,
  CanvasRenderer,
]);
export default echarts;

En él importamos todos los recursos que vamos a necesitar de Echarts y preparamos una instancia a medida con el método “use”.

Luego usaremos esta instancia para declarar gráficos de distintos tipos.

Empecemos con un gráfico de tipo radar.

import echarts from "./initEcharts";
const radarChart = echarts.init(document.querySelector(".radar-chart"));
radarChart.setOption({
  tooltip: {},
  radar: {
    shape: "circle",
    indicator: [
      { name: "Agilidad", max: 100 },
      { name: "Fuerza", max: 100 },
      { name: "Resistencia", max: 100 },
      { name: "Técnica", max: 100 },
      { name: "Carisma", max: 100 },
    ],
  },
  series: [],
});
export default radarChart;

Como ves, importamos la instancia de “echarts” que hemos generado y con “init()” pedimos a Echarts que prepare un nuevo gráfico.

Acto seguido, preparamos las características básicas con el método “setOption”, fijate como aquí indicamos el tipo mediante la propiedad “radar”.

  • Agregamos un “tooltip”
  • Definimos la forma e indicadores del radar.

De momento no incluimos datos ya que eso lo haremos de forma dinámica más adelante.

El siguiente gráfico que dejamos preparado será el de tipo calendario.

import echarts from "./initEcharts";
const calendarChart = echarts.init(document.querySelector(".calendar-chart"));
calendarChart.setOption({
  visualMap: {
    show: false,
    min: 0,
    max: 1,
    inRange: {
      color: ["#2eb675", "#fcc950"],
    },
  },
  calendar: {
    range: "2024-01",
    cellSize: [40, 40],
  },
  series: {
    type: "heatmap",
    coordinateSystem: "calendar",
    data: [],
  },
});
export default calendarChart;

Nuevamente construimos un objeto “chart” nuevo, y lo preparamos con un objeto a medida a través del método setOption.

  • Declaramos las características del mapa visual.
  • Definimos las propiedades del calendario, como su rango de tiempo y el tamaño de las celdas.

Hacemos lo propio con los gráficos de tipo Sankey y de tipo “queso” (o pastel).

import echarts from "./initEcharts";
const sankeyChart = echarts.init(document.querySelector(".sankey-chart"));
sankeyChart.setOption({
  series: {
    type: "sankey",
    layout: "none",
    emphasis: {
      focus: "adjacency",
    },
    links: [],
    data: [],
  },
});
export default sankeyChart;
  • Especificamos el tipo de gráfico.
  • Otras propiedades dentro de la serie.
import echarts from "./initEcharts";
const pieChart = echarts.init(document.querySelector(".pie-chart"));
pieChart.setOption({
  tooltip: {
    trigger: "item",
  },
  legend: {
    top: "5%",
    left: "center",
  },
  series: [
    {
      name: "Access From",
      type: "pie",
      radius: ["40%", "70%"],
      avoidLabelOverlap: false,
      itemStyle: {
        borderRadius: 10,
        borderColor: "#fff",
        borderWidth: 2,
      },
      label: {
        show: false,
        position: "center",
      },
      emphasis: {
        label: {
          show: true,
          fontSize: 40,
          fontWeight: "bold",
        },
      },
      labelLine: {
        show: false,
      },
      data: [],
    },
  ],
});
export default pieChart;
  • Agregamos un tooltip
  • Añadimos una leyenda
  • Declaramos características de las series

Con los 4 gráficos listos para ser representados, vayamos ahora a pedir los datos y construir la interfaz dinámicamente.

Iniciamos “main.js” importando los recursos.

import "./style.css";
import radarChart from "./scripts/radarChart";
import calendarChart from "./scripts/calendarChart";
import sankeyChart from "./scripts/sankeyChart";
import pieChart from "./scripts/pieChart";

Guardamos una referencia al DOM del contenedor donde irán los personajes seleccionables.

const charactersContainer = document.querySelector(".characters");

Preparamos una variable “charactersData” donde guardaremos los datos obtenidos del servidor.

let charactersData;

Declaramos una función llamada «createCharactersUI» encargada de agregar las interfaces de selección de cada luchador, a partir del argumento que recibe como parámetro.

const createCharactersUI = (wrestlers) => {
  for (let i = 0, j = wrestlers.length; i < j; i++) {
    const wrestler = wrestlers[i];
    const character = document.createElement("div");
    character.dataset.index = i;
    character.classList.add("character");
    if (i === 0) character.classList.add("selected");
    character.innerHTML = `
      <h2>${wrestler.name}</h2>
      <img src="${wrestler.snap}" alt="${wrestler.name}" />
      <p>${wrestler.description}</p>
    `;
    charactersContainer.appendChild(character);
    character.addEventListener("click", (event) => {
      document
        .querySelectorAll(".character")
        .forEach((character) => character.classList.remove("selected"));
      const selectedCharacter = event.currentTarget.dataset.index;
      updateCharts(selectedCharacter);
      event.currentTarget.classList.add("selected");
    });
  }
};

También incluimos la escucha de eventos “click” sobre esas tarjetas, y actualizamos los estilos acorde al cambio.

Seguimos declarando una segunda función que tiene como objetivo actualizar los gráficos en función del personaje seleccionado.

Veamos punto por punto la lógica de la función «updateCharts«.

SelectedCharacter será un índice de 0 a 2 (uno para cada personaje seleccionable), bien pues usaremos este índice para obtener los datos específicos dentro del array de “charactersData”.

Con esos datos definimos la propiedad series del objeto de configuración para el gráfico “radarChart”.

const updateCharts = (selectedCharacter) => {
  radarChart.setOption({
    series: [
      {
        name: charactersData[selectedCharacter].name,
        type: "radar",
        areaStyle: {
          color: "#fcc950",
        },
        lineStyle: {
          color: "#063920",
        },
        itemStyle: {
          color: "#063920",
        },
        data: [
          {
            value: Object.values(charactersData[selectedCharacter].radar_chart),
          },
        ],
      },
    ],
  });
};

En el caso del gráfico de tipo Sankey es un poco más complejo.

let uniqueSankeyData = [];
const sources = charactersData[selectedCharacter].sankey_data.map(
  (data) => data.source
);
const targets = charactersData[selectedCharacter].sankey_data.map(
  (data) => data.target
);
uniqueSankeyData = uniqueSankeyData.concat(sources);
uniqueSankeyData = uniqueSankeyData.concat(targets);
uniqueSankeyData = [...new Set(uniqueSankeyData)];
sankeyChart.setOption({
  series: {
    data: uniqueSankeyData.map((data) => ({ name: data })),
    links: charactersData[selectedCharacter].sankey_data,
  },
});

Tenemos que recorrer los valores de “sankey_data” dentro de los datos del personaje, y luego eliminar elementos duplicados.

Posteriormente podremos lanzar “setOption” con las propiedades “data” y “links” necesarias para dibujar este tipo de gráficos.

Actualizar los gráficos de calendario y pastel es mucho más inmediato.

calendarChart.setOption({
  series: {
    type: "heatmap",
    coordinateSystem: "calendar",
    data: charactersData[selectedCharacter].victories,
  },
});
pieChart.setOption({
  series: {
    data: charactersData[selectedCharacter].tournaments,
  },
});

Cerramos el script escuchando el evento de “resize” de la ventana para actualizar el tamaño de todos los gráficos.

window.addEventListener("resize", () => {
  pieChart.resize();
  radarChart.resize();
  calendarChart.resize();
  sankeyChart.resize();
});

Y por último hacemos una llamada al recurso “JSON” en el servidor para obtener los datos de los tres personajes y poder llamar a las funciones “createCharactersUI” y “updateCharts”.

window.addEventListener("load", async () => {
  try {
    const request = await fetch("./data.json");
    const data = await request.json();
    charactersData = data.wrestlers;
    createCharactersUI(charactersData);
    updateCharts(0);
  } catch (error) {
    console.error(error);
  }
});

Enhorabuena por haber llegado hasta aquí, espero que se haya entendido todo bien.

Si no es el caso, te agradecería que me escribieras en los comentarios y trataré de mejorar el post con tu ayuda.

Últimas reflexiones y recursos adicionales

Con este pequeño vistazo supongo que ya entenderás porqué Echarts es una librería “declarativa”, en realidad no hemos hecho otra cosa que llamar a métodos con objetos que describen la forma y tipología del gráfico.

Echarts es una herramienta que agrega una capa de abstracción, para facilitar la vida a los desarrolladores que necesitan dibujar gráficos dinámicos.

Sin embargo, si te apasiona el tema y quieres una alternativa muy potente a más bajo nivel, sin duda tienes que echarle un vistazo a D3, el peso pesado en materia de visualización de datos.

Te dejo algunos recursos adicionales por si quieres seguir aprendiendo:

Gracias por leer libreriasJs, hasta la próxima!

Deja un comentario