Démos D3.js interactives
Trois visualisations codées from scratch avec D3.js v7 — données fictives réalistes, animations soignées, responsive.
D3.js · scaleBand · scaleLinear · area
Chargement…
// Données fictives réalistes
const data: MonthData[] = [
{ month: 'Oct 25', visits: 2100, conversions: 78 },
{ month: 'Nov 25', visits: 2340, conversions: 91 },
// ...12 mois de données
];
// Bar chart D3 avec animation entrée depuis le bas
const x = d3.scaleBand().domain(data.map(d => d.month)).padding(0.35);
const y = d3.scaleLinear().domain([0, d3.max(data, d => d.visits)! * 1.1]);
g.selectAll('.bar').data(data).enter().append('rect')
.attr('y', innerH).attr('height', 0) // part du bas
.transition().duration(600)
.attr('y', d => y(d.visits))
.attr('height', d => innerH - y(d.visits));
// Line chart — effet dessin stroke-dashoffset
const len = path.node().getTotalLength();
path.attr('stroke-dasharray', len).attr('stroke-dashoffset', len)
.transition().duration(900).attr('stroke-dashoffset', 0);
// Tooltip : ref DOM direct (pas de useState → pas de re-render → pas de boucle)
const tip = tooltipRef.current!;
bar.on('mouseenter', (event, d) => {
tip.innerHTML = `<strong>${d.month}</strong><span>${d.visits} visites</span>`;
tip.style.opacity = '1';
}).on('mouseleave', () => { tip.style.opacity = '0'; });D3.js · forceSimulation · drag · zoom
Chargement…
// Force simulation — réseau de 17 nœuds tech
const sim = d3.forceSimulation<Node>(nodes)
.force('link', d3.forceLink<Node, Link>(links).id(d => d.id)
.distance(d => 80 + (1 - d.strength) * 60))
.force('charge', d3.forceManyBody().strength(-280))
.force('center', d3.forceCenter(W / 2, H / 2))
.force('collide', d3.forceCollide<Node>().radius(d => RADIUS[d.level] + 10));
// Drag behavior
const drag = d3.drag<SVGGElement, Node>()
.on('start', (e, d) => { d.fx = d.x; d.fy = d.y; sim.alphaTarget(0.3).restart(); })
.on('drag', (e, d) => { d.fx = e.x; d.fy = e.y; })
.on('end', (e, d) => { d.fx = null; d.fy = null; sim.alphaTarget(0); });
// Zoom behavior
const zoom = d3.zoom<SVGSVGElement, unknown>()
.scaleExtent([0.4, 3])
.on('zoom', e => zoomG.attr('transform', e.transform));
// Tick loop : repositionner liens et nœuds à chaque frame
sim.on('tick', () => {
linkEl.attr('x1', d => d.source.x!).attr('y1', d => d.source.y!)
.attr('x2', d => d.target.x!).attr('y2', d => d.target.y!);
nodeG.attr('transform', d => `translate(${d.x},${d.y})`);
});D3.js · geoPath · scaleSequential
Chargement…
// Projection conique conforme centrée sur la France
const projection = d3.geoConicConformal()
.center([2.454071, 46.279229])
.parallels([44, 49])
.scale(W * 4.2)
.translate([W / 2, H / 2]);
const path = d3.geoPath().projection(projection);
// Échelle couleur séquentielle violet → opaque
const colorScale = d3.scaleSequential()
.domain([vMin, vMax])
.interpolator(d3.interpolate(
'rgba(139,92,246,0.08)',
'rgba(139,92,246,0.95)'
));
// Fetch GeoJSON + rendu
d3.json(GEOJSON_URL).then(geojson => {
svg.selectAll('path')
.data(geojson.features)
.enter().append('path')
.attr('d', path)
.attr('fill', f => colorScale(REGION_DATA[f.properties.nom]))
// Animation décalée : chaque région apparaît à +40ms
.attr('opacity', 0)
.transition().duration(500).delay((_, i) => i * 40)
.attr('opacity', 1);
});