Visualización de datos con JavaScript

Cómo representar datos estadísticos en el navegador: histogramas con Canvas, gráficos de barras con SVG y librerías ligeras.

Los números solos no comunican. Un histograma o un gráfico de barras revela patrones que una tabla de frecuencias oculta. En esta lección construimos visualizaciones desde cero con las APIs nativas del navegador.


Gráfico de barras con Canvas

El elemento <canvas> permite dibujar con JavaScript. Es la opción de más bajo nivel pero con control total.

<canvas id="grafico" width="500" height="300"></canvas>
const datos = { HTML: 42, CSS: 35, JavaScript: 78, Python: 55, TypeScript: 48 };

const canvas = document.querySelector('#grafico');
const ctx = canvas.getContext('2d');

const labels = Object.keys(datos);
const valores = Object.values(datos);
const maxVal = Math.max(...valores);

const margen = 40;
const anchoBarra = (canvas.width - margen * 2) / labels.length;
const alturaDisponible = canvas.height - margen * 2;

// Fondo
ctx.fillStyle = '#1e1e2e';
ctx.fillRect(0, 0, canvas.width, canvas.height);

labels.forEach((label, i) => {
  const altBarra = (valores[i] / maxVal) * alturaDisponible;
  const x = margen + i * anchoBarra + anchoBarra * 0.1;
  const y = canvas.height - margen - altBarra;
  const ancho = anchoBarra * 0.8;

  // Barra
  ctx.fillStyle = '#fd98a8';
  ctx.fillRect(x, y, ancho, altBarra);

  // Etiqueta
  ctx.fillStyle = '#ffffff';
  ctx.font = '12px monospace';
  ctx.textAlign = 'center';
  ctx.fillText(label, x + ancho / 2, canvas.height - margen + 16);

  // Valor
  ctx.fillText(valores[i], x + ancho / 2, y - 6);
});

Histograma desde una tabla de frecuencias

Reutilizando la función frecuenciasIntervalos de la lección anterior:

function dibujarHistograma(canvas, frecuencias) {
  const ctx = canvas.getContext('2d');
  const maxFa = Math.max(...frecuencias.map((f) => f.fa));
  const margen = { top: 20, right: 20, bottom: 50, left: 40 };
  const w = canvas.width - margen.left - margen.right;
  const h = canvas.height - margen.top - margen.bottom;
  const anchoBarra = w / frecuencias.length;

  ctx.clearRect(0, 0, canvas.width, canvas.height);
  ctx.fillStyle = '#282828';
  ctx.fillRect(0, 0, canvas.width, canvas.height);

  frecuencias.forEach(({ intervalo, fa }, i) => {
    const altBarra = (fa / maxFa) * h;
    const x = margen.left + i * anchoBarra;
    const y = margen.top + h - altBarra;

    ctx.fillStyle = '#785082';
    ctx.fillRect(x + 2, y, anchoBarra - 4, altBarra);

    ctx.fillStyle = '#f6e6bb';
    ctx.font = '10px monospace';
    ctx.textAlign = 'center';
    ctx.fillText(intervalo, x + anchoBarra / 2, canvas.height - margen.bottom + 15);
    ctx.fillText(fa, x + anchoBarra / 2, y - 4);
  });
}

// Uso
const alturas = [162, 175, 180, 158, 171, 168, 183, 177, 165, 172,
                 169, 185, 160, 174, 179, 166, 182, 170, 163, 178];

const canvas = document.querySelector('#histograma');
dibujarHistograma(canvas, frecuenciasIntervalos(alturas));

Gráfico de barras con SVG

SVG es más accesible (el DOM es inspeccionable) y escala sin pérdida de calidad:

function barrasSVG(contenedor, datos) {
  const labels = Object.keys(datos);
  const valores = Object.values(datos);
  const maxVal = Math.max(...valores);
  const W = 500, H = 280;
  const margen = { top: 20, right: 20, bottom: 40, left: 30 };
  const anchoBarra = (W - margen.left - margen.right) / labels.length;
  const altDisp = H - margen.top - margen.bottom;

  const svgNS = 'http://www.w3.org/2000/svg';
  const svg = document.createElementNS(svgNS, 'svg');
  svg.setAttribute('viewBox', `0 0 ${W} ${H}`);
  svg.setAttribute('width', W);
  svg.setAttribute('height', H);

  labels.forEach((label, i) => {
    const altBarra = (valores[i] / maxVal) * altDisp;
    const x = margen.left + i * anchoBarra + anchoBarra * 0.1;
    const y = margen.top + altDisp - altBarra;
    const ancho = anchoBarra * 0.8;

    // Barra
    const rect = document.createElementNS(svgNS, 'rect');
    rect.setAttribute('x', x);
    rect.setAttribute('y', y);
    rect.setAttribute('width', ancho);
    rect.setAttribute('height', altBarra);
    rect.setAttribute('fill', '#fd98a8');
    svg.appendChild(rect);

    // Etiqueta
    const text = document.createElementNS(svgNS, 'text');
    text.setAttribute('x', x + ancho / 2);
    text.setAttribute('y', H - margen.bottom + 16);
    text.setAttribute('text-anchor', 'middle');
    text.setAttribute('font-size', '11');
    text.setAttribute('fill', '#ffffff');
    text.textContent = label;
    svg.appendChild(text);
  });

  contenedor.appendChild(svg);
}

barrasSVG(document.querySelector('#svg-contenedor'), {
  'Ene': 120, 'Feb': 98, 'Mar': 145, 'Abr': 132, 'May': 167,
});

Cuándo usar Canvas vs SVG

CaracterísticaCanvasSVG
TipoPixel (raster)Vectorial
EscaladoSe pixela al ampliarPerfecto a cualquier tamaño
DOMUn solo elemento opacoCada forma es un elemento del DOM
InteractividadHay que calcular manualmenteaddEventListener en cada forma
RendimientoMejor con miles de puntosMejor con pocos elementos
AccesibilidadDifícilBuena con aria-label

Alternativa: Chart.js

Para proyectos reales, una librería como Chart.js ahorra tiempo manteniendo control sobre el diseño:

npm install chart.js
import { Chart, BarController, BarElement, CategoryScale, LinearScale } from 'chart.js';

Chart.register(BarController, BarElement, CategoryScale, LinearScale);

new Chart(document.querySelector('#mi-grafico'), {
  type: 'bar',
  data: {
    labels: ['HTML', 'CSS', 'JavaScript', 'Python'],
    datasets: [{
      label: 'Popularidad',
      data: [42, 35, 78, 55],
      backgroundColor: '#fd98a8',
    }],
  },
  options: {
    responsive: true,
    plugins: { legend: { display: false } },
  },
});

En la última lección del curso introducimos la probabilidad básica: cómo cuantificar la incertidumbre y cómo simularla con JavaScript.