CoolFace
Apppublic

lerobot/robot-learning-tutorial

sourceHugging Faceupdated 1y agoView on Hugging Face
508likes
d3-bar.html318 linesDownload Raw Back to embeds
1<div class="d3-bar"  ></div>2<style>3  .d3-bar .controls { margin-top: 0; display: flex; gap: 16px; align-items: center; justify-content: flex-end; flex-wrap: wrap; }4  .d3-bar .controls .control-group { display: flex; flex-direction: column; align-items: flex-start; gap: 6px; }5  .d3-bar .controls label { font-size: 12px; color: var(--text-color); font-weight: 700; }6  .d3-bar .controls select { font-size: 12px; padding: 8px 28px 8px 10px; border: 1px solid var(--border-color); border-radius: 8px; background-color: var(--surface-bg); color: var(--text-color); background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%230f1115' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E"); background-repeat: no-repeat; background-position: right 8px center; background-size: 12px; -webkit-appearance: none; -moz-appearance: none; appearance: none; cursor: pointer; transition: border-color .15s ease, box-shadow .15s ease; }7  [data-theme="dark"] .d3-bar .controls select { background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%23ffffff' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E"); }8  .d3-bar .controls select:hover { border-color: var(--primary-color); }9  .d3-bar .controls select:focus { border-color: var(--primary-color); box-shadow: 0 0 0 3px rgba(232,137,171,.25); outline: none; }10  /* Header (legend + controls) placed after chart */11  .d3-bar .chart-header { display: flex; align-items: flex-start; justify-content: flex-start; gap: 12px; margin: 8px 0 0 0; flex-wrap: wrap; }12  .d3-bar .legend-bottom { display: flex; flex-direction: column; align-items: flex-start; gap: 6px; font-size: 12px; color: var(--text-color); }13  .d3-bar .legend-bottom .legend-title { font-size: 12px; font-weight: 700; color: var(--text-color); }14  .d3-bar .legend-bottom .items { display: flex; flex-wrap: wrap; gap: 8px 14px; }15  .d3-bar .legend-bottom .item { display: inline-flex; align-items: center; gap: 6px; white-space: nowrap; }16  .d3-bar .legend-bottom .swatch { width: 14px; height: 14px; border-radius: 3px; border: 1px solid var(--border-color); display: inline-block; }17  .d3-bar.hovering .legend-bottom .item.ghost { opacity: .35; }18  .d3-bar.hovering .bars path.ghost { opacity: .35; }19  .d3-bar .axis-label { fill: var(--text-color); font-size: 12px; font-weight: 700; }20  /* Apply axis/tick/grid purely via CSS */21  .d3-bar .axes path,22  .d3-bar .axes line { stroke: var(--axis-color); }23  .d3-bar .axes text { fill: var(--tick-color); }24  .d3-bar .grid line { stroke: var(--grid-color); }25  /* Tooltip improvements */26  .d3-bar .d3-tooltip { z-index: var(--z-tooltip); backdrop-filter: saturate(1.12) blur(8px); }27  /* Hover/transition styling for bars and legend */28  .d3-bar .bars path.bar { transition: opacity .12s ease, stroke .12s ease, stroke-width .12s ease; }29  .d3-bar .bars path.bar.highlight { stroke: none; stroke-width: 0; }30  .d3-bar.hovering .bars path.ghost { opacity: .25; }31  .d3-bar .legend-bottom .item.hovered { color: inherit; }32  .d3-bar .legend-bottom .item.hovered .swatch { border-color: var(--border-color); }33  .d3-bar .d3-tooltip .swatch { width: 12px; height: 12px; border-radius: 3px; border: 1px solid var(--border-color); display: inline-block; margin-right: 6px; vertical-align: -2px; }34  /* Chart card wrapper */35  .d3-bar .chart-card { background: var(--surface-bg); border: 1px solid var(--border-color); border-radius: 10px; padding: 8px; }36  /* Layout adjustments to give controls more space */37  .d3-bar .chart-header {38    padding-left: 8px;39    padding-right: 8px;40    gap: 20px;41  }42  .d3-bar .controls {43    justify-content: flex-start;44    min-width: 320px;45  }46  .d3-bar .controls .control-group {47    min-width: 150px;48  }49  .d3-bar .controls select {50    font-size: 13px;51    min-width: 160px;52  }53</style>54<script>55  (() => {56    const ensureD3 = (cb) => {57      if (window.d3 && typeof window.d3.select === 'function') return cb();58      let s = document.getElementById('d3-cdn-script');59      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); }60      const onReady = () => { if (window.d3 && typeof window.d3.select === 'function') cb(); };61      s.addEventListener('load', onReady, { once: true });62      if (window.d3) onReady();63    };64 65    const bootstrap = () => {66      const mount = document.currentScript ? document.currentScript.previousElementSibling : null;67      const container = (mount && mount.querySelector && mount.querySelector('.d3-bar')) || document.querySelector('.d3-bar');68      if (!container) return;69      if (container.dataset) { if (container.dataset.mounted === 'true') return; container.dataset.mounted = 'true'; }70 71      // Data, matching bar.py72      const seqLabels = ["1024","2048","4096","8192"];73      const seqScale = [1,2,4,8];74      const componentKeys = ['parameters','gradients','optimizer','activations'];75      const modelSizes = ["1B","3B","8B","70B","405B"];76      const paramsMem = { "1B":4.0, "3B":13.3, "8B":26.0, "70B":244.0, "405B":1520.0 };77      const actCoeff = { "1B":3.6, "3B":9.3, "8B":46.2, "70B":145.7, "405B":1519.9 };78      const recomputeModes = ["none","selective","full"];79 80      const activationsCurve = (sizeKey, mode) => {81        const coeff = actCoeff[sizeKey];82        let arr = seqScale.map((v) => coeff * (v * v));83        if (mode === 'selective') arr = arr.map((v) => v * 0.25);84        else if (mode === 'full') arr = arr.map((v) => v * (1 / 16));85        return arr;86      };87      const stackFor = (sizeKey, mode) => {88        const p = seqScale.map(() => paramsMem[sizeKey]);89        const g = seqScale.map(() => paramsMem[sizeKey]);90        const o = seqScale.map(() => 2*paramsMem[sizeKey]);91        const a = activationsCurve(sizeKey, mode);92        return { parameters: p, gradients: g, optimizer: o, activations: a };93      };94 95      const Y = {}; // Y[mode][size][component] => array96      recomputeModes.forEach((m) => {97        Y[m] = {}; modelSizes.forEach((s) => { Y[m][s] = stackFor(s, m); });98      });99 100      // Controls101      const controls = document.createElement('div');102      controls.className = 'controls';103      const groupSize = document.createElement('div'); groupSize.className = 'control-group';104      const labelSize = document.createElement('label'); labelSize.textContent = 'Model Size';105      const selSize = document.createElement('select'); modelSizes.forEach((s) => { const o = document.createElement('option'); o.value = s; o.textContent = s; selSize.appendChild(o); });106      groupSize.appendChild(labelSize); groupSize.appendChild(selSize);107      const groupRecomp = document.createElement('div'); groupRecomp.className = 'control-group';108      const labelRecomp = document.createElement('label'); labelRecomp.textContent = 'Recomputation';109      const selRecomp = document.createElement('select'); recomputeModes.forEach((m) => { const o = document.createElement('option'); o.value = m; o.textContent = m; selRecomp.appendChild(o); });110      groupRecomp.appendChild(labelRecomp); groupRecomp.appendChild(selRecomp);111 112      // Header (legend + controls) to be placed after chart113      const header = document.createElement('div'); header.className = 'chart-header';114      const legendBottom = document.createElement('div'); legendBottom.className = 'legend-bottom';115      const legendTitle = document.createElement('div'); legendTitle.className = 'legend-title'; legendTitle.textContent = 'Legend';116      const legendItems = document.createElement('div'); legendItems.className = 'items';117      legendBottom.appendChild(legendTitle); legendBottom.appendChild(legendItems);118      header.appendChild(legendBottom);119      header.appendChild(controls);120      // SVG scaffolding inside a card wrapper121      const card = document.createElement('div'); card.className = 'chart-card'; container.appendChild(card);122      // Place header after the chart card123      container.appendChild(header);124      const svg = d3.select(card).append('svg').attr('width','100%').style('display','block');125      const gRoot = svg.append('g');126      const gGrid = gRoot.append('g').attr('class','grid');127      const gAxes = gRoot.append('g').attr('class','axes');128      const gBars = gRoot.append('g').attr('class','bars');129 130      // Tooltip131      container.style.position = container.style.position || 'relative';132      let tip = container.querySelector('.d3-tooltip'); let tipInner;133      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; }134 135      // State136      let currentSize = modelSizes[0];137      let currentMode = 'selective';138      selRecomp.value = currentMode;139 140      // Layout & scales141      let width=800, height=360; const margin = { top: 16, right: 28, bottom: 56, left: 64 };142      const x0 = d3.scaleBand().paddingInner(0.25).paddingOuter(0.1); // groups (seq)143      const y = d3.scaleLinear();144      function getCategoricalColors(count){145        try {146          if (window.ColorPalettes && typeof window.ColorPalettes.getColors === 'function') {147            return window.ColorPalettes.getColors('categorical', count);148          }149        } catch(_) {}150        const primary = getComputedStyle(document.documentElement).getPropertyValue('--primary-color').trim() || '#E889AB';151        const tableau = (window.d3 && window.d3.schemeTableau10) ? window.d3.schemeTableau10 : ['#4e79a7','#f28e2b','#e15759','#76b7b2','#59a14f','#edc948','#b07aa1','#ff9da7','#9c755f','#bab0ab'];152        const pool = [primary, ...tableau];153        const arr = []; for (let i=0;i<count;i++){ arr.push(pool[i % pool.length]); }154        return arr;155      }156      const palette = getCategoricalColors(componentKeys.length);157      const colorMap = new Map(componentKeys.map((k,i)=>[k, palette[i]]));158      const colorOf = (key) => colorMap.get(key) || 'var(--primary-color)';159 160      function yMax(sizeKey, mode){161        const s = Y[mode][sizeKey];162        let max = 0; for (let i=0;i<seqLabels.length;i++){ const sum = s.parameters[i]+s.gradients[i]+s.optimizer[i]+s.activations[i]; if (sum>max) max=sum; }163        return max*1.05;164      }165 166      function renderLegend(){167        legendItems.innerHTML = componentKeys.map((key, i) => {168          const color = palette[i];169          return `<span class="item" data-key="${key}"><span class=\"swatch\" style=\"background:${color}\"></span><span>${key}</span></span>`;170        }).join('');171        legendItems.querySelectorAll('.item').forEach((el) => {172          el.addEventListener('mouseenter', () => {173            const k = el.getAttribute('data-key'); if (!k) return;174            container.classList.add('hovering');175            gBars.selectAll('path.bar').classed('ghost', d => d && d.key !== k);176            legendItems.querySelectorAll('.item').forEach(it => it.classList.toggle('ghost', it.getAttribute('data-key') !== k));177          });178          el.addEventListener('mouseleave', () => {179            container.classList.remove('hovering');180            gBars.selectAll('path.bar').classed('ghost', false);181            legendItems.querySelectorAll('.item').forEach(it => it.classList.remove('ghost'));182          });183        });184      }185 186      function updateScales(){187        width = container.clientWidth || 800; height = Math.max(260, Math.round(width/3)); svg.attr('width', width).attr('height', height);188        const innerWidth = width - margin.left - margin.right; const innerHeight = height - margin.top - margin.bottom; gRoot.attr('transform', `translate(${margin.left},${margin.top})`);189 190        x0.domain(seqLabels).range([0, innerWidth]);191        y.domain([0, yMax(currentSize, currentMode)]).range([innerHeight, 0]).nice();192 193        // Grid194        gGrid.selectAll('*').remove();195        gGrid.selectAll('line').data(y.ticks(6)).join('line')196          .attr('x1', 0).attr('x2', innerWidth).attr('y1', (d)=>y(d)).attr('y2', (d)=>y(d))197          .attr('stroke', 'var(--grid-color)').attr('stroke-width', 1).attr('shape-rendering', 'crispEdges');198 199        // Axes200        gAxes.selectAll('*').remove();201        gAxes.append('g').attr('transform', `translate(0,${innerHeight})`).call(d3.axisBottom(x0)).call((g)=>{ g.selectAll('path, line').attr('stroke', 'var(--axis-color)'); g.selectAll('text').attr('fill', 'var(--tick-color)').style('font-size','12px'); });202        gAxes.append('g').call(d3.axisLeft(y).ticks(6).tickFormat(d3.format('~f'))).call((g)=>{ g.selectAll('path, line').attr('stroke', 'var(--axis-color)'); g.selectAll('text').attr('fill', 'var(--tick-color)').style('font-size','12px'); });203 204        // Axis labels205        gAxes.append('text').attr('class','axis-label axis-label--x').attr('x', innerWidth/2).attr('y', innerHeight + 44).attr('text-anchor','middle').text('Sequence Length');206        gAxes.append('text').attr('class','axis-label axis-label--y').attr('text-anchor','middle').attr('transform', `translate(${-52},${innerHeight/2}) rotate(-90)`).text('Memory (GB)');207 208        renderLegend();209 210        return { innerWidth, innerHeight };211      }212 213      function drawBars(){214        const stacks = Y[currentMode][currentSize];215        const series = componentKeys.map((key, i)=>({ key, color: palette[i], values: stacks[key] }));216        // Stack values217        const stacked = seqLabels.map((label, i) => {218          let acc = 0; const items = [];219          series.forEach((s, idx) => {220            const y0 = acc; const y1 = acc + s.values[i];221            items.push({ key: s.key, color: s.color, i, y0, y1, xLabel: label, value: s.values[i], isBottom: idx === 0, isTop: idx === series.length - 1 });222            acc = y1;223          });224          const total = acc;225          items.forEach(it => { it.total = total; });226          return { label, items };227        });228 229        const { innerWidth, innerHeight } = updateScales();230 231        const bandWidth = x0.bandwidth();232        const groups = gBars.selectAll('g.bar-group').data(stacked, d=>d.label);233        const groupsEnter = groups.enter().append('g').attr('class','bar-group');234        groupsEnter.merge(groups).attr('transform', (d)=>`translate(${x0(d.label)},0)`);235        groups.exit().remove();236 237        // Helper to draw per-corner rounded rectangle path238        const rCorner = 4;239        const roundedPath = (x, yTop, w, h, isTop, isBottom) => {240          const r = Math.min(rCorner, Math.max(0, Math.min(w, h) / 2));241          const rTL = isTop ? r : 0, rTR = isTop ? r : 0, rBR = isBottom ? r : 0, rBL = isBottom ? r : 0;242          const x0 = x, y0 = yTop, x1 = x + w, y1 = yTop + h;243          return `M${x0 + rTL},${y0}`244            + `H${x1 - rTR}`245            + (rTR ? `Q${x1},${y0} ${x1},${y0 + rTR}` : `V${y0}`)246            + `V${y1 - rBR}`247            + (rBR ? `Q${x1},${y1} ${x1 - rBR},${y1}` : `H${x1}`)248            + `H${x0 + rBL}`249            + (rBL ? `Q${x0},${y1} ${x0},${y1 - rBL}` : `V${y1}`)250            + `V${y0 + rTL}`251            + (rTL ? `Q${x0},${y0} ${x0 + rTL},${y0}` : `H${x0}`)252            + 'Z';253        };254 255        const bars = groupsEnter.merge(groups).selectAll('path.bar').data(d=>d.items, d=>d.key);256        bars.enter().append('path').attr('class','bar')257          .attr('d', (d)=> roundedPath(0, y(d.y1), bandWidth, Math.max(0.5, y(d.y0) - y(d.y1)), d.isTop, d.isBottom))258          .attr('fill', (d)=>d.color)259          .on('mouseenter', function(ev, d){260            container.classList.add('hovering');261            gBars.selectAll('path.bar').classed('ghost', (dd) => !(dd && dd.key === d.key));262            const pct = d.total > 0 ? (d.value / d.total * 100) : 0;263            tipInner.innerHTML = `264              <div style="display:flex;align-items:center;gap:6px;margin-bottom:4px;">265                <span class="swatch" style="background:${d.color}"></span>266                <strong>${d.key}</strong>267              </div>268              <div><strong>Seq</strong> ${d.xLabel}</div>269              <div><strong>Mem</strong> ${d.value.toFixed(1)} GB <span style="opacity:.7">(${pct.toFixed(0)}%)</span></div>270              <div style="opacity:.7"><strong>Total</strong> ${d.total.toFixed(1)} GB</div>271            `;272            tip.style.opacity = '1';273            const li = legendItems.querySelector(`.item[data-key="${d.key}"]`);274            if (li) li.classList.add('hovered');275            legendItems.querySelectorAll('.item').forEach(it => it.classList.toggle('ghost', it.getAttribute('data-key') !== d.key));276          })277          .on('mousemove', function(ev, d){278            const [mx, my] = d3.pointer(ev, container);279            const offsetX = 12, offsetY = 12;280            const maxX = (container.clientWidth || 0) - (tip.offsetWidth + 6);281            const maxY = (container.clientHeight || 0) - (tip.offsetHeight + 6);282            const tx = Math.max(0, Math.min(mx + offsetX, maxX));283            const ty = Math.max(0, Math.min(my + offsetY, maxY));284            tip.style.transform = `translate(${Math.round(tx)}px, ${Math.round(ty)}px)`;285          })286          .on('mouseleave', function(){287            tip.style.opacity='0';288            tip.style.transform='translate(-9999px, -9999px)';289            container.classList.remove('hovering');290            gBars.selectAll('path.bar').classed('ghost', false).classed('highlight', false);291            legendItems.querySelectorAll('.item').forEach(it => { it.classList.remove('hovered'); it.classList.remove('ghost'); });292          })293          .merge(bars)294          .transition().duration(200)295          .attr('d', (d)=> roundedPath(0, y(d.y1), bandWidth, Math.max(0.5, y(d.y0) - y(d.y1)), d.isTop, d.isBottom))296          .attr('fill', (d)=>d.color);297        bars.exit().remove();298      }299 300      function update(){ drawBars(); }301 302      // Boot303      update();304      // controls already appended to footer; populate control groups305      controls.appendChild(groupSize); controls.appendChild(groupRecomp);306      selSize.addEventListener('change', (e)=>{ currentSize = e.target.value; update(); });307      selRecomp.addEventListener('change', (e)=>{ currentMode = e.target.value; update(); });308 309      const rerender = () => { update(); };310      if (window.ResizeObserver) { const ro = new ResizeObserver(()=>rerender()); ro.observe(container); } else { window.addEventListener('resize', rerender); }311    };312 313    if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => ensureD3(bootstrap), { once: true }); } else { ensureD3(bootstrap); }314  })();315</script>316 317 318