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>