Guide D3.js avec Claude Code pour la visualisation de données
Créez des graphiques D3.js avec Claude Code : TypeScript exécutable, cas d'usage, pièges, vérification et CTA.
Pourquoi utiliser Claude Code avec D3.js
Avec D3.js, la difficulté n’est pas seulement de dessiner une première barre. Elle arrive quand il faut comprendre comment scaleBand, axisLeft, selection.join et transition fonctionnent ensemble, puis garder le graphique fiable avec de vraies données, du responsive, de l’accessibilité et des changements produit.
Claude Code aide parce qu’il peut lire le HTML, le CSS, le TypeScript, les contrats de données et les conventions du projet avant de produire le code. D3.js relie les données au DOM et au SVG ; une consigne floue donne souvent un graphique qui marche une fois, mais qui devient fragile.
Gardez ces liens officiels à portée de main : D3 Getting started, Joining data, d3-scale, d3-axis et d3-transition.
Modèle mental pour débuter
D3 transforme des données en pixels et en éléments SVG. Nommer chaque étape rend les prompts plus précis.
| Élément | Explication simple | Rôle dans l’exemple |
|---|---|---|
| selection | Sélectionner des éléments du DOM | Ajouter un svg dans #chart |
| scale | Convertir une valeur en position écran | Placer les canaux sur x et les conversions sur y |
| axis | Dessiner graduations et libellés | Afficher canaux et nombres de conversion |
| mark | Forme visible | Barres rect et ligne path |
| join | Associer lignes de données et nœuds DOM | Mettre à jour les barres quand les données changent |
| transition | Animer un changement | Faire pousser les barres depuis la ligne de base |
Un prompt utile donne la structure de données, les unités, les états vides, le clavier et la vérification.
Construis un graphique en barres responsive en Vite + TypeScript + D3 v7 pour les visiteurs et conversions par canal. Utilise
selection.join, tooltip, focus clavier,aria-label, état sans données et test rapide dans la console.
Exemple D3.js + TypeScript exécutable
Créez l’app avec npm create vite@latest d3-demo -- --template vanilla-ts, installez npm i d3 @types/d3, remplacez ces fichiers, puis lancez 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="fr">
<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),
)}.`;
}
Vérification rapide dans la console :
console.log(document.querySelectorAll("#chart rect.bar").length);
console.log(document.querySelector("#chart svg")?.getAttribute("role"));
console.log(document.querySelector("#chart-summary")?.textContent);
Cas d’usage concrets
| Cas | Pourquoi D3 convient | Ce que Claude Code doit faire |
|---|---|---|
| Funnel de contenu | Comparer source, catégorie et clic CTA | Reprendre le plan de mesure analytics |
| Dashboard SaaS | Montrer usage, rétention et écarts par plan | Verrouiller types, chargement, état vide et segments |
| Reporting opérationnel | Mettre en avant seuils et anomalies | Vérifier la charge avec performance |
| Résultats A/B test | Visualiser les écarts de conversion | Séparer calcul et affichage comme dans A/B testing |
La valeur métier vient quand le graphique explique une action liée au revenu : inscription, achat, contact, renouvellement ou activation produit.
Pièges fréquents
| Piège | Symptôme | Correction |
|---|---|---|
| Redessiner sans nettoyage | Barres et axes se dupliquent | Nettoyer le conteneur ou retourner une fonction cleanup |
| Ignorer les scales | Le mobile ou les nouvelles valeurs cassent le rendu | Passer les valeurs par scaleLinear, scaleBand ou une autre scale |
| Dessiner seulement enter | Les anciennes données restent visibles | Utiliser selection.data(...).join(...) |
| Tooltip uniquement souris | Le clavier n’a pas l’information | Ajouter tabindex, aria-label et focus |
| Conflit avec React ou Astro | Le framework écrase le DOM modifié par D3 | Isoler D3 dans un conteneur |
| Sens porté seulement par la couleur | Accessibilité faible | Ajouter libellés, valeurs, légende et contraste ; voir accessibilité |
Demandez une revue explicite : doublons au redraw, tableau vide, division par zéro, clavier, lecteurs d’écran, largeur mobile et 1000 points.
SEO, monétisation et vérification
Un article D3.js attire souvent du trafic, mais il faut relier le code à un objectif mesurable et à des liens internes comme TypeScript, SEO et design system.
ClaudeCodeLab propose des supports de configuration Claude Code, des prompts de revue, des modèles CLAUDE.md et du conseil. Pour transformer ce snippet en standard d’équipe, consultez les produits ou la page training et consultation.
Lors de l’essai pratique, le plus gros gain est venu de la demande initiale d’accessibilité et de vérification. Ajouter aria-label, focus clavier, état vide et smoke test dès le premier prompt coûte moins cher que les corriger ensuite.
PDF gratuit: cheatsheet Claude Code
Saisissez votre email et téléchargez une page avec commandes, habitudes de review et workflow sûr.
Nous protégeons vos données et n'envoyons pas de spam.
À propos de l'auteur
Masa
Ingénieur spécialisé dans les workflows pratiques avec Claude Code.
Articles liés
Workflow Obsidian vers CLAUDE.md avec Claude Code
Transformer des notes Obsidian en notes CLAUDE.md concises pour reprendre les sessions sans réexpliquer.
Claude Code Revenue CTA Routing : relier articles, PDF, Gumroad et consultation
Un workflow Claude Code pour orienter les lecteurs vers PDF gratuit, Gumroad ou consultation selon l'intention.
Règles de handoff Claude Code en équipe: preuves, permissions, rollback et revenus
Un format concret pour transmettre un travail Claude Code avec preuves, permissions, rollback, PDF gratuit, Gumroad et consultation.