Spaces:
Running
Running
File size: 9,479 Bytes
b9e7b9b c24ea90 b9e7b9b |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 |
<div class="d3-train-diagram" style="width:100%;margin:10px 0;"></div>
<div class="caption">Survolez les blocs pour afficher une explication.</div>
<style>
.d3-train-diagram + .caption { margin-top: 8px; font-size: 14px; color: var(--muted-color); }
</style>
<script>
(() => {
const ensureD3 = (cb) => {
if (window.d3 && typeof window.d3.select === 'function') return cb();
let s = document.getElementById('d3-cdn-script');
if (!s) { s = document.createElement('script'); s.id = 'd3-cdn-script'; s.src = 'https://cdn.jsdelivr.net/npm/d3@7/dist/d3.min.js'; document.head.appendChild(s); }
const onReady = () => { if (window.d3 && typeof window.d3.select === 'function') cb(); };
s.addEventListener('load', onReady, { once: true });
if (window.d3) onReady();
};
const bootstrap = () => {
const mount = document.currentScript ? document.currentScript.previousElementSibling : null;
const container = (mount && mount.querySelector && mount.querySelector('.d3-train-diagram')) || document.querySelector('.d3-train-diagram');
if (!container) return;
if (container.dataset) { if (container.dataset.mounted === 'true') return; container.dataset.mounted = 'true'; }
// Diagram spec
const numBlocks = 7;
const rows = [
{ key: 'model', label: 'Model', color: '#a78bfa' },
{ key: 'forward', label: 'Forward', color: '#14b8a6' },
{ key: 'backward', label: 'Backward', color: '#f59e0b' },
{ key: 'gradients', label: 'Gradients', color: 'var(--primary-color)' },
{ key: 'optimization', label: 'Optimization', color: '#10b981' },
{ key: 'updated', label: 'Updated', color: '#7c3aed' },
];
const hoverText = {
model: 'Chaque bloc représente un sous-module du modèle.',
forward: 'Propagation avant: calcul des activations couche par couche.',
backward: 'Rétropropagation: calcul des gradients via la chaîne.',
gradients: 'Accumulateurs de gradients pour chaque couche.',
optimization: 'Étape d’optimisation: mise à jour des poids.',
updated: 'Paramètres mis à jour, prêts pour l’itération suivante.'
};
// SVG
const svg = d3.select(container).append('svg').attr('width', '100%').style('display','block');
const gRoot = svg.append('g');
const gLegend = gRoot.append('foreignObject').attr('class','legend');
const gArrows = gRoot.append('g').attr('class','arrows');
const gBlocks = gRoot.append('g').attr('class','blocks');
const gLabels = gRoot.append('g').attr('class','row-labels');
// Tooltip (reuse style from others)
container.style.position = container.style.position || 'relative';
let tip = container.querySelector('.d3-tooltip'); let tipInner;
if (!tip) { tip = document.createElement('div'); tip.className = 'd3-tooltip'; Object.assign(tip.style,{ position:'absolute', top:'0px', left:'0px', transform:'translate(-9999px, -9999px)', pointerEvents:'none', padding:'8px 10px', borderRadius:'8px', fontSize:'12px', lineHeight:'1.35', border:'1px solid var(--border-color)', background:'var(--surface-bg)', color:'var(--text-color)', boxShadow:'0 4px 24px rgba(0,0,0,.18)', opacity:'0', transition:'opacity .12s ease' }); tipInner = document.createElement('div'); tipInner.className = 'd3-tooltip__inner'; tipInner.style.textAlign='left'; tip.appendChild(tipInner); container.appendChild(tip); } else { tipInner = tip.querySelector('.d3-tooltip__inner') || tip; }
// Layout
let width=800, height=360; const margin = { top: 24, right: 180, bottom: 40, left: 32 };
const x = d3.scaleBand().domain(d3.range(numBlocks)).paddingInner(0.2).paddingOuter(0.05);
const y = d3.scaleBand().domain(d3.range(rows.length)).paddingInner(0.35);
function updateScales(){
width = container.clientWidth || 800;
const rowH = Math.max(54, Math.min(80, Math.round(width / 12)));
const innerHeight = rows.length * rowH;
height = innerHeight + margin.top + margin.bottom;
svg.attr('width', width).attr('height', height);
const innerWidth = width - margin.left - margin.right;
gRoot.attr('transform', `translate(${margin.left},${margin.top})`);
x.range([0, innerWidth]);
y.range([0, innerHeight]);
return { innerWidth, innerHeight };
}
function render(){
const { innerWidth, innerHeight } = updateScales();
// Legend right side
const legendWidth = 160, legendHeight = rows.length * 20;
gLegend.attr('x', innerWidth + 16).attr('y', 0).attr('width', legendWidth).attr('height', legendHeight);
const lroot = gLegend.selectAll('div').data([0]).join('xhtml:div');
lroot.html(`
<div style="display:flex;flex-direction:column;gap:8px;">
${rows.map(r => `<div style=\"display:flex;align-items:center;gap:8px;\"><span style=\"width:14px;height:14px;background:${r.color};border-radius:4px;display:inline-block\"></span><span>${r.label}</span></div>`).join('')}
</div>
`);
// Row labels on the right side aligned to centers
gLabels.selectAll('*').remove();
gLabels.selectAll('text').data(rows).join('text')
.attr('x', innerWidth + 16)
.attr('y', (_,i)=> y(i) + y.bandwidth()/2)
.attr('dy','0.35em')
.style('font-size','14px')
.style('fill','var(--text-color)')
.text(d=>d.label);
// Blocks per row
const blockW = Math.min(84, x.bandwidth());
const blockH = Math.min(52, Math.round(y.bandwidth() * 0.8));
const blocks = [];
rows.forEach((row, ri) => {
for (let i=0;i<numBlocks;i++) blocks.push({ row, ri, i });
});
const sel = gBlocks.selectAll('rect.block').data(blocks, d=>`${d.row.key}-${d.i}`);
sel.join(
enter => enter.append('rect').attr('class','block')
.attr('x', d=>x(d.i))
.attr('y', d=>y(d.ri) + (y.bandwidth()-blockH)/2)
.attr('rx', 12).attr('ry', 12)
.attr('width', blockW)
.attr('height', blockH)
.attr('fill', d=>d.row.color)
.attr('opacity', 0.95)
.attr('stroke', 'rgba(0,0,0,0.18)')
.attr('filter', 'url(#shadow)')
.on('mouseenter', function(ev, d){
d3.select(this).attr('opacity', 1.0).attr('stroke-width', 1.2);
tipInner.innerHTML = `<div><strong>${d.row.label}</strong></div><div>${hoverText[d.row.key]}</div>`;
tip.style.opacity = '1';
})
.on('mousemove', function(ev){ const [mx,my] = d3.pointer(ev, container); tip.style.transform = `translate(${mx+12}px, ${my+12}px)`; })
.on('mouseleave', function(){ tip.style.opacity='0'; tip.style.transform='translate(-9999px,-9999px)'; d3.select(this).attr('opacity', 0.95).attr('stroke-width', 1); })
);
// Arrows forward/backward
gArrows.selectAll('*').remove();
const arrowY = (ri) => y(ri) + y.bandwidth()/2;
const isDark = document.documentElement.getAttribute('data-theme') === 'dark';
const arrowColor = isDark ? 'rgba(255,255,255,0.55)' : 'rgba(0,0,0,0.55)';
const defs = svg.select('defs').empty() ? svg.append('defs') : svg.select('defs');
const marker = defs.append('marker').attr('id','arrow').attr('viewBox','0 0 10 10').attr('refX', 10).attr('refY', 5).attr('markerWidth', 6).attr('markerHeight', 6).attr('orient','auto-start-reverse');
marker.append('path').attr('d','M 0 0 L 10 5 L 0 10 z').attr('fill', arrowColor);
// drop shadow filter
const flt = defs.append('filter').attr('id','shadow').attr('x','-20%').attr('y','-20%').attr('width','140%').attr('height','140%');
flt.append('feDropShadow').attr('dx','0').attr('dy','1').attr('stdDeviation','1.5').attr('flood-color','rgba(0,0,0,0.18)');
// Forward arrow (top orientation)
gArrows.append('line').attr('x1', x(0)).attr('y1', arrowY(1)-28).attr('x2', x(numBlocks-1)+blockW).attr('y2', arrowY(1)-28)
.attr('stroke', rows[1].color).attr('stroke-width', 4).attr('marker-end','url(#arrow)');
// Backward arrow (orange, reversed)
gArrows.append('line').attr('x1', x(numBlocks-1)+blockW).attr('y1', arrowY(2)-20).attr('x2', x(0)).attr('y2', arrowY(2)-20)
.attr('stroke', rows[2].color).attr('stroke-width', 4).attr('marker-end','url(#arrow)');
// Vertical arrows (gradients down, updated up)
const midX = x(3) + blockW/2;
gArrows.append('line').attr('x1', midX).attr('y1', arrowY(2)+blockH/2+4).attr('x2', midX).attr('y2', arrowY(3)-blockH/2-6)
.attr('stroke', rows[3].color).attr('stroke-width', 3).attr('marker-end','url(#arrow)');
gArrows.append('line').attr('x1', midX).attr('y1', arrowY(4)+blockH/2+6).attr('x2', midX).attr('y2', arrowY(5)-blockH/2-6)
.attr('stroke', rows[5].color).attr('stroke-width', 3).attr('marker-end','url(#arrow)');
}
render();
if (window.ResizeObserver) { const ro = new ResizeObserver(()=>render()); ro.observe(container); } else { window.addEventListener('resize', render); }
};
if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => ensureD3(bootstrap), { once: true }); } else { ensureD3(bootstrap); }
})();
</script>
|