Visualización de datos con JavaScript
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ística | Canvas | SVG |
|---|---|---|
| Tipo | Pixel (raster) | Vectorial |
| Escalado | Se pixela al ampliar | Perfecto a cualquier tamaño |
| DOM | Un solo elemento opaco | Cada forma es un elemento del DOM |
| Interactividad | Hay que calcular manualmente | addEventListener en cada forma |
| Rendimiento | Mejor con miles de puntos | Mejor con pocos elementos |
| Accesibilidad | Difícil | Buena 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.