CoolFace
Apppublic

lerobot/robot-learning-tutorial

sourceHugging Faceupdated 1y agoView on Hugging Face
508likes
throughput-drops-comparison.html336 linesDownload Raw Back to embeds
1<div class="throughput-drops-comparison"></div>2<style>3  .throughput-drops-comparison { position: relative; }4  .throughput-drops-comparison .axis-label { fill: var(--text-color); font-size: 12px; font-weight: 700; }5  .throughput-drops-comparison .axes path, .throughput-drops-comparison .axes line { stroke: var(--axis-color); }6  .throughput-drops-comparison .axes text { fill: var(--tick-color); }7  .throughput-drops-comparison .grid line { stroke: var(--grid-color); }8  .throughput-drops-comparison .chart-card { background: var(--surface-bg); border: 1px solid var(--border-color); border-radius: 10px; padding: 8px; }9  .throughput-drops-comparison .chart-header { display:flex; align-items:flex-start; justify-content:flex-start; gap:12px; margin: 8px 0 0 0; flex-wrap: wrap; }10  .throughput-drops-comparison .legend-bottom { display:flex; align-items:center; justify-content:flex-start; font-size:12px; color: var(--text-color); }11  .throughput-drops-comparison .legend-bottom .items { display:flex; flex-wrap:wrap; gap:8px 14px; }12  .throughput-drops-comparison .legend-bottom .item { display:inline-flex; align-items:center; gap:6px; white-space:nowrap; }13  .throughput-drops-comparison .legend-bottom .swatch { width:14px; height:14px; border-radius:3px; border:1px solid var(--border-color); display:inline-block; }14  .throughput-drops-comparison .legend-bottom .legend-title { font-size: 12px; font-weight: 700; color: var(--text-color); }15  .throughput-drops-comparison .legend-bottom { flex-direction: column; align-items: flex-start; gap: 6px; }16  .throughput-drops-comparison .lines path.active { stroke-width: 3; }17  .throughput-drops-comparison .d3-tooltip { z-index: var(--z-elevated); backdrop-filter: saturate(1.12) blur(8px); }18  .throughput-drops-comparison .d3-tooltip__inner { display:flex; flex-direction:column; gap:6px; min-width: 220px; }19  .throughput-drops-comparison .d3-tooltip__inner > div:first-child { font-weight: 800; letter-spacing: 0.1px; margin-bottom: 0; }20  .throughput-drops-comparison .d3-tooltip__inner > div:nth-child(2) { font-size: 11px; color: var(--muted-color); display: block; margin-top: -4px; margin-bottom: 2px; letter-spacing: 0.1px; }21  .throughput-drops-comparison .d3-tooltip__color-dot { display:inline-block; width: 12px; height: 12px; border-radius: 3px; border: 1px solid var(--border-color); }22  /* Ghosting on hover */23  .throughput-drops-comparison.hovering .legend-bottom .item.ghost { opacity: .35; }24  .throughput-drops-comparison.hovering .lines path.ghost { opacity: .25; }25  .throughput-drops-comparison.hovering .points circle.ghost { opacity: .25; }26</style>27<script>28  (() => {29    const ensureD3 = (cb) => {30      if (window.d3 && typeof window.d3.select === 'function') return cb();31      let s = document.getElementById('d3-cdn-script');32      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); }33      const onReady = () => { if (window.d3 && typeof window.d3.select === 'function') cb(); };34      s.addEventListener('load', onReady, { once: true }); if (window.d3) onReady();35    };36 37    const bootstrap = () => {38      const scriptEl = document.currentScript;39      let container = scriptEl ? scriptEl.previousElementSibling : null;40      if (!(container && container.classList && container.classList.contains('throughput-drops-comparison'))){41        const cs = Array.from(document.querySelectorAll('.throughput-drops-comparison')).filter(el => !(el.dataset && el.dataset.mounted === 'true'));42        container = cs[cs.length - 1] || null;43      }44      if (!container) return;45      if (container.dataset) { if (container.dataset.mounted === 'true') return; container.dataset.mounted = 'true'; }46 47      // Tooltip48      container.style.position = container.style.position || 'relative';49      let tip = container.querySelector('.d3-tooltip'); let tipInner;50      if (!tip) {51        tip = document.createElement('div'); tip.className = 'd3-tooltip';52        Object.assign(tip.style, {53          position:'absolute', top:'0px', left:'0px', transform:'translate(-9999px, -9999px)', pointerEvents:'none',54          padding:'8px 10px', borderRadius:'8px', fontSize:'12px', lineHeight:'1.35', border:'1px solid var(--border-color)',55          background:'var(--surface-bg)', color:'var(--text-color)', boxShadow:'0 4px 24px rgba(0,0,0,.18)', opacity:'0', transition:'opacity .12s ease'56        });57        tipInner = document.createElement('div'); tipInner.className = 'd3-tooltip__inner'; tipInner.style.textAlign='left'; tip.appendChild(tipInner); container.appendChild(tip);58      } else { tipInner = tip.querySelector('.d3-tooltip__inner') || tip; }59 60      // Header (legend) placed after the chart61      const header = document.createElement('div'); header.className = 'chart-header';62      const legendBottom = document.createElement('div'); legendBottom.className = 'legend-bottom'; header.appendChild(legendBottom);63 64      // Chart card (SVG)65      const card = document.createElement('div'); card.className = 'chart-card'; container.appendChild(card);66      container.appendChild(header);67      68      // SVG69      const svg = d3.select(card).append('svg').attr('width','100%').style('display','block');70      const gRoot = svg.append('g');71      const gGrid = gRoot.append('g').attr('class','grid');72      const gAxes = gRoot.append('g').attr('class','axes');73      const gLines = gRoot.append('g').attr('class','lines');74      const gPoints = gRoot.append('g').attr('class','points');75      const overlay = gRoot.append('rect').attr('fill','transparent').style('cursor','crosshair');76      const hoverLine = gRoot.append('line').attr('stroke-width',1).style('display','none');77 78      // State/data79      let width = 800, height = 480; const margin = { top: 16, right: 32, bottom: 44, left: 80 };80      const xScale = d3.scaleLinear();81      const yScale = d3.scaleLinear();82      const lineGen = d3.line().x(d => xScale(d.step)).y(d => yScale(d.value));83      let data = [];84 85      // Colors - following guidelines to use ColorPalettes86      let currentColors = ['var(--primary-color, #4e79a7)', 'var(--primary-color, #4e79a7)'];87      88      function refreshPalette(){89        try { 90          if (window.ColorPalettes && typeof window.ColorPalettes.getColors === 'function') {91            const colors = window.ColorPalettes.getColors('categorical', 2);92            if (colors && colors.length >= 2) {93              currentColors = colors;94              // Re-render with new colors95              if (data.length > 0) render();96              return;97            }98          }99        } catch(_){}100        // Fallback to CSS variable or default101        currentColors = ['var(--primary-color, #4e79a7)', '#e15759'];102        // Re-render with fallback colors103        if (data.length > 0) render();104      }105      106      function getColors(){107        return currentColors;108      }109      110      // Format helper for thousands (5000 -> 5k, 1500 -> 1.5k)111      function formatK(v){112        const abs = Math.abs(v);113        if (abs >= 1000) {114          const n = v / 1000;115          const s = d3.format('.1f')(n);116          return (s.endsWith('.0') ? s.slice(0, -2) : s) + 'k';117        }118        return d3.format('d')(v);119      }120 121      // Format helper for throughput values122      function formatThroughput(v){123        if (v >= 1000) {124          return d3.format('.1f')(v / 1000) + 'k';125        }126        return d3.format('.1f')(v);127      }128 129      function updateLayout(){130        const axisColor = getComputedStyle(container).getPropertyValue('--axis-color').trim() || 'rgba(0,0,0,0.25)';131        width = container.clientWidth || 800;132        height = Math.max(280, Math.round(width / 3));133        svg.attr('width', width).attr('height', height);134        gRoot.attr('transform', `translate(${margin.left},${margin.top})`);135        const innerWidth = width - margin.left - margin.right;136        const innerHeight = height - margin.top - margin.bottom;137        overlay.attr('x',0).attr('y',0).attr('width', innerWidth).attr('height', innerHeight);138        hoverLine.attr('y1',0).attr('y2', innerHeight).attr('stroke', axisColor);139        return { innerWidth, innerHeight };140      }141 142      function render(){143        if (data.length === 0) return;144        145        const { innerWidth, innerHeight } = updateLayout();146        147        // Sort data by step148        const sortedData = data.slice().sort((a, b) => a.step - b.step);149        150        // Prepare series data151        const series = [152          {153            name: 'Throughput (main run)',154            values: sortedData.map(d => ({ step: d.step, value: d.throughput_drops }))155          },156          {157            name: 'Throughput (ablations)',158            values: sortedData.map(d => ({ step: d.step, value: d.throughput_no_drops }))159          }160        ];161        162        // domains163        const minStep = d3.min(sortedData, d => d.step);164        const maxStep = d3.max(sortedData, d => d.step);165        const minValue = d3.min(sortedData, d => Math.min(d.throughput_drops, d.throughput_no_drops));166        const maxValue = d3.max(sortedData, d => Math.max(d.throughput_drops, d.throughput_no_drops));167        168        xScale.domain([minStep, maxStep]).range([0, innerWidth]);169        yScale.domain([minValue, maxValue]).nice().range([innerHeight, 0]);170 171        // grid172        gGrid.selectAll('*').remove();173        gGrid.selectAll('line').data(yScale.ticks(6)).join('line')174          .attr('x1',0).attr('x2', innerWidth).attr('y1', d=>yScale(d)).attr('y2', d=>yScale(d))175          .attr('stroke','var(--grid-color)').attr('stroke-width',1).attr('shape-rendering','crispEdges');176 177        // axes178        gAxes.selectAll('*').remove();179        gAxes.append('g').attr('transform', `translate(0,${innerHeight})`).call(d3.axisBottom(xScale).ticks(8).tickFormat(formatK)).call(g=>{ g.selectAll('path, line').attr('stroke','var(--axis-color)'); g.selectAll('text').attr('fill','var(--tick-color)').style('font-size','12px'); });180        gAxes.append('g').call(d3.axisLeft(yScale).ticks(6).tickFormat(formatThroughput)).call(g=>{ g.selectAll('path, line').attr('stroke','var(--axis-color)'); g.selectAll('text').attr('fill','var(--tick-color)').style('font-size','12px'); });181        gAxes.append('text').attr('class','axis-label').attr('text-anchor','middle').attr('x', innerWidth/2).attr('y', innerHeight + 38).text('Training Step');182        gAxes.append('text').attr('class','axis-label').attr('text-anchor','middle').attr('transform', `translate(${-60}, ${innerHeight/2}) rotate(-90)`).text('Tokens/sec/GPU');183 184        // lines185        const colors = getColors();186        gLines.selectAll('*').remove();187        series.forEach((s, i) => {188          gLines.append('path')189            .attr('class', `line line-${i}`)190            .attr('data-series', s.name)191            .attr('fill','none')192            .attr('stroke', colors[i % colors.length])193            .attr('stroke-width', 2)194            .attr('d', lineGen(s.values));195        });196 197        // point markers198        gPoints.selectAll('*').remove();199        series.forEach((s, i) => {200          gPoints.selectAll(`circle.point-${i}`).data(s.values).join('circle')201            .attr('class', `point point-${i}`)202            .attr('data-series', s.name)203            .attr('r', 2)204            .attr('fill', colors[i % colors.length])205            .attr('fill-opacity', 0.6)206            .attr('cx', d=>xScale(d.step))207            .attr('cy', d=>yScale(d.value));208        });209 210        // legend211        legendBottom.innerHTML = `<div class="legend-title">Legend</div><div class="items">${series.map((s, i) => `<span class="item" data-series="${s.name}"><span class="swatch" style="background:${colors[i % colors.length]}"></span><span>${s.name}</span></span>`).join('')}</div>`;212 213        // Legend hover → ghost lines/points214        try {215          const legendNode = legendBottom;216          legendNode.querySelectorAll('.item').forEach(el => {217            el.addEventListener('mouseenter', () => {218              const seriesName = el.getAttribute('data-series'); if (!seriesName) return;219              container.classList.add('hovering');220              gLines.selectAll('path.line').classed('ghost', s => s.getAttribute && s.getAttribute('data-series') !== seriesName);221              gPoints.selectAll('circle.point').classed('ghost', p => p.getAttribute && p.getAttribute('data-series') !== seriesName);222              legendNode.querySelectorAll('.item').forEach(it => it.classList.toggle('ghost', it.getAttribute('data-series') !== seriesName));223            });224            el.addEventListener('mouseleave', () => {225              container.classList.remove('hovering');226              gLines.selectAll('path.line').classed('ghost', false);227              gPoints.selectAll('circle.point').classed('ghost', false);228              legendNode.querySelectorAll('.item').forEach(it => it.classList.remove('ghost'));229            });230          });231        } catch {}232 233        // hover234        function onMove(ev){235          const [mx, my] = d3.pointer(ev, overlay.node());236          const sx = xScale.invert(mx);237          238          // Find nearest step239          const steps = Array.from(new Set(sortedData.map(d => d.step))).sort((a,b) => a - b);240          const nearest = steps.reduce((best, s) => Math.abs(s - sx) < Math.abs(best - sx) ? s : best, steps[0]);241          const xpx = xScale(nearest);242          hoverLine.style('display', null).attr('x1', xpx).attr('x2', xpx);243          244          // Find data point for this step245          const dataPoint = sortedData.find(d => d.step === nearest);246          if (!dataPoint) return;247          248          // tooltip content249          let html = `<div style="font-weight:800;letter-spacing:.1px;">Throughput Comparison</div><div style="font-size:11px;color:var(--muted-color);margin-top:-4px;margin-bottom:2px;">Step ${formatK(nearest)}</div>`;250          251          series.forEach((s, i) => {252            const value = s.name === 'Throughput (main run)' ? dataPoint.throughput_drops : dataPoint.throughput_no_drops;253            html += `<div style="display:flex;align-items:center;gap:6px;white-space:nowrap;"><span class="d3-tooltip__color-dot" style="background:${colors[i % colors.length]}"></span><strong>${s.name}</strong><span style="margin-left:auto;">${formatThroughput(value)}</span></div>`;254          });255          256          tipInner.innerHTML = html; 257          tip.style.opacity = '1'; 258          tip.style.transform = `translate(${Math.round(mx + margin.left + 12)}px, ${Math.round(my + margin.top + 12)}px)`;259        }260        261        function onLeave(){ 262          tip.style.opacity='0'; 263          tip.style.transform='translate(-9999px, -9999px)'; 264          hoverLine.style('display','none'); 265        }266        267        overlay.on('mousemove', onMove).on('mouseleave', onLeave);268      }269 270      // load CSV and init271      (async () => {272        try {273          // Try multiple possible paths for the CSV file274          const csvPaths = [275            '/data/throughput_drops_comparison_before_after.csv',276            './assets/data/throughput_drops_comparison_before_after.csv',277            '../assets/data/throughput_drops_comparison_before_after.csv',278            '../../assets/data/throughput_drops_comparison_before_after.csv'279          ];280          281          let csvText = null;282          for (const path of csvPaths) {283            try {284              const response = await fetch(path, { cache: 'no-cache' });285              if (response.ok) {286                csvText = await response.text();287                break;288              }289            } catch(_) {}290          }291          292          if (!csvText) {293            throw new Error('CSV file not found: throughput_drops_comparison_before_after.csv');294          }295          296          const rows = d3.csvParse(csvText);297          298          // Parse the data299          data = rows.map(d => ({300            step: +d.Step,301            throughput_drops: +d.throughput_drops,302            throughput_no_drops: +d.throughput_no_drops303          })).filter(d => !isNaN(d.step) && !isNaN(d.throughput_drops) && !isNaN(d.throughput_no_drops));304          305          // Initialize palette and listen for changes306          refreshPalette();307          document.addEventListener('palettes:updated', refreshPalette);308          309          render();310          311          const rerender = () => render();312          if (window.ResizeObserver) { 313            const ro = new ResizeObserver(() => rerender()); 314            ro.observe(container); 315          } else { 316            window.addEventListener('resize', rerender); 317          }318        } catch (e) {319          const pre = document.createElement('pre'); 320          pre.textContent = 'CSV load error: ' + (e && e.message ? e.message : e);321          pre.style.color = 'var(--danger, #b00020)'; 322          pre.style.fontSize = '12px'; 323          pre.style.whiteSpace = 'pre-wrap'; 324          container.appendChild(pre);325        }326      })();327    };328 329    if (document.readyState === 'loading') { 330      document.addEventListener('DOMContentLoaded', () => ensureD3(bootstrap), { once: true }); 331    } else { 332      ensureD3(bootstrap); 333    }334  })();335</script>336