Use Cases (Actualizado: 2/6/2026)

Guía para implementar visualizaciones D3.js con Claude Code

Crea gráficos D3.js con Claude Code: TypeScript ejecutable, casos de uso, errores comunes, verificación y CTA.

Guía para implementar visualizaciones D3.js con Claude Code

Por qué combinar Claude Code y D3.js

El primer problema con D3.js no suele ser dibujar una barra. El problema aparece cuando hay que entender cómo se conectan scaleBand, axisLeft, selection.join y transition, y después mantener el gráfico con datos reales, redimensionado, accesibilidad y cambios de producto.

Claude Code ayuda porque puede leer el HTML, CSS, TypeScript, contratos de datos y convenciones del proyecto antes de generar código. D3.js une datos con DOM y SVG; si la petición es vaga, el resultado puede funcionar una vez, pero será difícil de revisar.

Usa esta guía junto con la documentación oficial: D3 Getting started, Joining data, d3-scale, d3-axis y d3-transition.

Modelo mental para principiantes

D3 convierte datos en píxeles y elementos SVG. Definir cada capa evita que el prompt sea ambiguo.

ParteExplicación simplePapel en el ejemplo
selectionElegir elementos del DOMCrear un svg dentro de #chart
scaleConvertir valores en posiciones de pantallaPasar canales al eje x y conversiones al eje y
axisDibujar marcas y etiquetasMostrar canales y conteos de conversión
markForma visibleBarras con rect y línea auxiliar con path
joinEmparejar filas de datos con nodos DOMActualizar barras cuando cambian los datos
transitionAnimar un cambio visualHacer crecer las barras desde la base

Un buen prompt no dice solo “haz un gráfico D3”. Incluye forma de datos, unidades, estados vacíos, teclado y prueba de verificación.

Crea un gráfico de barras responsive en Vite + TypeScript + D3 v7 para visitantes y conversiones por canal. Usa selection.join, tooltip, foco de teclado, aria-label, estado sin datos y una prueba rápida en la consola del navegador.

Ejemplo D3.js + TypeScript ejecutable

Crea una app con npm create vite@latest d3-demo -- --template vanilla-ts, instala npm i d3 @types/d3, reemplaza estos archivos y ejecuta npm run dev.

{
  "scripts": {
    "dev": "vite"
  },
  "dependencies": {
    "d3": "^7.9.0"
  },
  "devDependencies": {
    "@types/d3": "^7.4.3",
    "typescript": "latest",
    "vite": "latest"
  }
}
<!doctype html>
<html lang="es">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>D3 Conversion Chart</title>
  </head>
  <body>
    <main class="page">
      <h1>D3.js Conversion Dashboard</h1>
      <section class="chart-shell" aria-describedby="chart-summary">
        <div id="chart"></div>
        <p id="chart-summary" class="sr-only"></p>
      </section>
    </main>
    <script type="module" src="/src/main.ts"></script>
  </body>
</html>
:root {
  color: #172033;
  background: #f7f7f3;
  font-family: Inter, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
}

body {
  margin: 0;
}

.page {
  width: min(920px, calc(100vw - 32px));
  margin: 40px auto;
}

.chart-shell {
  border: 1px solid #d8d5cc;
  border-radius: 8px;
  background: #ffffff;
  padding: 20px;
}

#chart {
  min-height: 320px;
  position: relative;
}

#chart svg {
  display: block;
  width: 100%;
  height: auto;
  overflow: visible;
}

.axis-label {
  fill: #475569;
  font-size: 12px;
}

.bar {
  fill: #2563eb;
  outline: none;
}

.bar:hover,
.bar:focus {
  fill: #dc2626;
}

.trend-line {
  fill: none;
  stroke: #0f172a;
  stroke-width: 2;
  pointer-events: none;
}

.chart-tooltip {
  position: absolute;
  top: 0;
  left: 0;
  max-width: 220px;
  border-radius: 6px;
  background: #172033;
  color: #ffffff;
  font-size: 13px;
  line-height: 1.5;
  opacity: 0;
  padding: 8px 10px;
  pointer-events: none;
  transform: translate(-9999px, -9999px);
  transition: opacity 120ms ease;
}

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
}
import * as d3 from "d3";
import "./style.css";

type ChannelDatum = {
  channel: string;
  visitors: number;
  conversions: number;
};

const data: ChannelDatum[] = [
  { channel: "Search", visitors: 4200, conversions: 168 },
  { channel: "Newsletter", visitors: 2600, conversions: 182 },
  { channel: "Social", visitors: 3100, conversions: 96 },
  { channel: "Partner", visitors: 1400, conversions: 84 },
];

const numberFormat = new Intl.NumberFormat(undefined);
const percentFormat = new Intl.NumberFormat(undefined, {
  style: "percent",
  maximumFractionDigits: 1,
});

function conversionRate(datum: ChannelDatum): number {
  return datum.visitors === 0 ? 0 : datum.conversions / datum.visitors;
}

function drawConversionChart(container: HTMLElement, items: ChannelDatum[]): void {
  container.replaceChildren();

  if (items.length === 0) {
    container.textContent = "No data to display.";
    return;
  }

  const margin = { top: 28, right: 24, bottom: 56, left: 64 };
  const outerWidth = 760;
  const outerHeight = 420;
  const width = outerWidth - margin.left - margin.right;
  const height = outerHeight - margin.top - margin.bottom;

  const svg = d3
    .select(container)
    .append("svg")
    .attr("viewBox", `0 0 ${outerWidth} ${outerHeight}`)
    .attr("role", "img")
    .attr("aria-labelledby", "chart-title chart-desc");

  svg.append("title").attr("id", "chart-title").text("Conversions by channel");
  svg
    .append("desc")
    .attr("id", "chart-desc")
    .text("Bar chart comparing conversions from each acquisition channel.");

  const plot = svg
    .append("g")
    .attr("transform", `translate(${margin.left},${margin.top})`);

  const x = d3
    .scaleBand<string>()
    .domain(items.map((d) => d.channel))
    .range([0, width])
    .padding(0.28);

  const y = d3
    .scaleLinear()
    .domain([0, d3.max(items, (d) => d.conversions) ?? 0])
    .nice()
    .range([height, 0]);

  plot
    .append("g")
    .attr("transform", `translate(0,${height})`)
    .call(d3.axisBottom(x))
    .call((axis) => axis.selectAll("text").attr("dy", "0.85em"));

  plot.append("g").call(d3.axisLeft(y).ticks(5));

  plot
    .append("text")
    .attr("class", "axis-label")
    .attr("x", -margin.left + 4)
    .attr("y", -10)
    .text("Conversions");

  const tooltip = d3.select(container).append("div").attr("class", "chart-tooltip");

  function showTooltip(event: PointerEvent | FocusEvent, datum: ChannelDatum): void {
    const xCenter = (x(datum.channel) ?? 0) + x.bandwidth() / 2 + margin.left;
    const yTop = y(datum.conversions) + margin.top;
    const [left, top] =
      "clientX" in event ? d3.pointer(event, container) : [xCenter, yTop];

    tooltip
      .style("opacity", "1")
      .style("transform", `translate(${left + 12}px, ${top - 28}px)`)
      .html(
        `<strong>${datum.channel}</strong><br />` +
          `Visitors: ${numberFormat.format(datum.visitors)}<br />` +
          `Conversions: ${numberFormat.format(datum.conversions)}<br />` +
          `CVR: ${percentFormat.format(conversionRate(datum))}`,
      );
  }

  function hideTooltip(): void {
    tooltip.style("opacity", "0").style("transform", "translate(-9999px, -9999px)");
  }

  const bars = plot
    .selectAll<SVGRectElement, ChannelDatum>("rect.bar")
    .data(items, (d) => d.channel)
    .join((enter) =>
      enter
        .append("rect")
        .attr("class", "bar")
        .attr("x", (d) => x(d.channel) ?? 0)
        .attr("width", x.bandwidth())
        .attr("y", height)
        .attr("height", 0),
    )
    .attr("tabindex", 0)
    .attr("role", "img")
    .attr(
      "aria-label",
      (d) =>
        `${d.channel}: ${numberFormat.format(d.conversions)} conversions, ${percentFormat.format(
          conversionRate(d),
        )} conversion rate`,
    )
    .on("pointerenter pointermove", showTooltip)
    .on("focus", showTooltip)
    .on("pointerleave blur", hideTooltip);

  bars
    .transition()
    .duration(700)
    .delay((_d, index) => index * 80)
    .attr("x", (d) => x(d.channel) ?? 0)
    .attr("width", x.bandwidth())
    .attr("y", (d) => y(d.conversions))
    .attr("height", (d) => height - y(d.conversions));

  const trendLine = d3
    .line<ChannelDatum>()
    .x((d) => (x(d.channel) ?? 0) + x.bandwidth() / 2)
    .y((d) => y(d.conversions))
    .curve(d3.curveMonotoneX);

  plot
    .append("path")
    .datum(items)
    .attr("class", "trend-line")
    .attr("d", trendLine);
}

const chart = document.querySelector<HTMLElement>("#chart");

if (!chart) {
  throw new Error("Missing #chart element.");
}

drawConversionChart(chart, data);

const summary = document.querySelector<HTMLElement>("#chart-summary");

if (summary) {
  const best = data.reduce((current, item) =>
    conversionRate(item) > conversionRate(current) ? item : current,
  );

  summary.textContent = `Highest conversion rate: ${best.channel}, ${percentFormat.format(
    conversionRate(best),
  )}.`;
}

Prueba rápida en la consola:

console.log(document.querySelectorAll("#chart rect.bar").length);
console.log(document.querySelector("#chart svg")?.getAttribute("role"));
console.log(document.querySelector("#chart-summary")?.textContent);

Casos de uso reales

CasoPor qué D3.js encajaQué pedirle a Claude Code
Embudo de contenidosCompara fuente, categoría y clics de CTAReutilizar la medición de analytics
Dashboard SaaSMuestra uso de funciones, retención y diferencias por planFijar tipos, loading, empty state y segmentos en TypeScript
Operaciones e incidenciasResalta umbrales, anomalías y periodosRevisar coste de render con performance
A/B testingEnseña diferencias de conversión mejor que una tablaSeparar cálculo y visualización como en A/B testing

El valor aparece cuando el gráfico explica acciones cercanas a ingresos: registro, compra, contacto, renovación o activación.

Errores comunes

ErrorSíntomaSolución
Redibujar sin limpiarBarras y ejes se duplicanLimpiar el contenedor o devolver una función de cleanup
Saltarse las escalasEl gráfico se rompe en móvil o con rangos nuevosPasar siempre por scaleLinear, scaleBand u otra escala
Usar solo enterQuedan elementos antiguosUsar selection.data(...).join(...)
Tooltip solo con ratónUsuarios de teclado no ven la informaciónAñadir tabindex, aria-label y focus
Choque con frameworksReact o Astro sobrescribe el DOMEncerrar D3 en un contenedor propio
Significado solo por colorMenor accesibilidadAñadir etiquetas, valores y contraste; ver accesibilidad

Pide una revisión concreta: duplicados al redibujar, arrays vacíos, división por cero, teclado, lectores de pantalla, ancho móvil y 1000 puntos de datos.

SEO, monetización y verificación

Un artículo de D3.js puede atraer búsquedas, pero conviene conectarlo con una acción de negocio y con enlaces internos como TypeScript tips, SEO y design system.

ClaudeCodeLab ofrece materiales de configuración, prompts de revisión, plantillas CLAUDE.md y consultoría. Si tu equipo necesita reglas de visualización y no solo un snippet, revisa la página de productos o training y consultoría.

En la prueba práctica, el mayor ahorro vino de pedir accesibilidad y verificación desde el primer prompt. Añadir aria-label, foco de teclado, estado vacío y smoke test al inicio fue más barato que corregirlo después.

#Claude Code #D3.js #visualización de datos #gráficos #frontend
Gratis

PDF gratis: cheatsheet de Claude Code

Introduce tu email y descarga una hoja con comandos, hábitos de revisión y flujos seguros.

Cuidamos tus datos y no enviamos spam.

Masa

Sobre el autor

Masa

Ingeniero enfocado en workflows prácticos con Claude Code.