CoolFace
Apppublic

lerobot/robot-learning-tutorial

sourceHugging Faceupdated 1y agoView on Hugging Face
508likes
d3-pie.html155 linesDownload Raw Back to embeds
1<div class="d3-pie"></div>2<style>3  .d3-pie { position: relative; }4  .d3-pie .legend { display:flex; flex-direction:column; align-items:flex-start; gap:6px; margin: 8px 0 0 0; font-size:12px; color: var(--text-color); }5  .d3-pie .legend .legend-title { font-size:12px; font-weight:700; color: var(--text-color); }6  .d3-pie .legend .items { display:flex; flex-wrap:wrap; gap:8px 14px; }7  .d3-pie .legend .item { display:inline-flex; align-items:center; gap:6px; white-space:nowrap; }8  .d3-pie .legend .swatch { width:14px; height:14px; border-radius:3px; border:1px solid var(--border-color); }9  /* Ghost legend items when hovering slices */10  .d3-pie.hovering .legend .item.ghost { opacity: .35; }11  /* Ghost effect on slices */12  .d3-pie .slice { transition: opacity .15s ease; }13  .d3-pie.hovering .slice.ghost { opacity: .25; }14  /* Labels with contrast liseret */15  .d3-pie .slice-label { font-size: 11px; font-weight: 700; fill: var(--text-color); paint-order: stroke; stroke: var(--transparent-page-contrast); stroke-width: 3px; }16  .d3-pie .d3-tooltip { position:absolute; top:0; left:0; transform:translate(-9999px,-9999px); pointer-events:none; padding:8px 10px; border-radius:8px; font-size:12px; line-height:1.35; border:1px solid var(--border-color); background:var(--surface-bg); color:var(--text-color); box-shadow:0 4px 24px rgba(0,0,0,.18); opacity:0; transition:opacity .12s ease; }17  .d3-pie .d3-tooltip { z-index: var(--z-elevated); backdrop-filter: saturate(1.12) blur(8px); }18  .d3-pie .d3-tooltip__inner { display:flex; flex-direction:column; gap:6px; min-width: 220px; text-align: left; }19  .d3-pie .d3-tooltip__inner > div:first-child { font-weight: 800; letter-spacing: 0.1px; margin-bottom: 0; }20  .d3-pie .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  .d3-pie .d3-tooltip__inner > div:nth-child(n+3) { padding-top: 6px; border-top: 1px solid var(--border-color); }22  .d3-pie .d3-tooltip .swatch { width:12px; height:12px; border-radius:3px; border:1px solid var(--border-color); display:inline-block; margin-right:6px; }23  .d3-pie .chart-card { background: var(--surface-bg); border: 1px solid var(--border-color); border-radius: 10px; padding: 8px; }24</style>25<script>26  (() => {27    const ensureD3 = (cb) => {28      if (window.d3 && typeof window.d3.select === 'function') return cb();29      let s = document.getElementById('d3-cdn-script');30      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); }31      const onReady = () => { if (window.d3 && typeof window.d3.select === 'function') cb(); };32      s.addEventListener('load', onReady, { once:true }); if (window.d3) onReady();33    };34 35    const bootstrap = () => {36      const scriptEl = document.currentScript;37      let container = scriptEl ? scriptEl.previousElementSibling : null;38      if (!(container && container.classList && container.classList.contains('d3-pie'))){39        const cs = Array.from(document.querySelectorAll('.d3-pie')).filter(el => !(el.dataset && el.dataset.mounted==='true'));40        container = cs[cs.length-1] || null;41      }42      if (!container) return;43      if (container.dataset) { if (container.dataset.mounted==='true') return; container.dataset.mounted='true'; }44 45      container.style.position = container.style.position || 'relative';46      let tip = container.querySelector('.d3-tooltip'); let tipInner;47      if (!tip) { tip = document.createElement('div'); tip.className = 'd3-tooltip'; tipInner = document.createElement('div'); tipInner.className='d3-tooltip__inner'; tip.appendChild(tipInner); container.appendChild(tip); } else { tipInner = tip.querySelector('.d3-tooltip__inner') || tip; }48 49      const card = document.createElement('div'); card.className = 'chart-card'; container.appendChild(card);50      const legend = document.createElement('div'); legend.className = 'legend'; legend.innerHTML = '<div class="legend-title">Legend</div><div class="items"></div>'; container.appendChild(legend);51 52      const svg = d3.select(card).append('svg').attr('width','100%').style('display','block');53      const gRoot = svg.append('g');54 55      const DEFAULT_CSV = '/data/vision.csv';56      const fetchFirstAvailable = async (paths) => {57        for (const p of paths) { try { const r = await fetch(p, { cache:'no-cache' }); if (r.ok) return await r.text(); } catch(_){} }58        throw new Error('CSV not found: vision.csv');59      };60      function parseCsv(text){61        return d3.csvParse(text, d => ({62          category: (d['eagle_cathegory']||d['category']||'').trim(),63          value: +((d['total_samples']||d['value']||'0').toString().trim()) || 064        }));65      }66 67      let width=800, height=340; const DONUT_INNER_RATIO = 0.6;68      function updateSize(){69        width = container.clientWidth || 800; height = Math.max(240, Math.round(width/3));70        svg.attr('width', width).attr('height', height);71        gRoot.attr('transform', `translate(${width/2},${height/2})`);72        return { inner: Math.min(width, height) * 0.42 };73      }74 75      function makeLegend(categories, colorOf){76        const items = legend.querySelector('.items'); items.innerHTML = '';77        categories.forEach(name => { const el = document.createElement('span'); el.className='item'; el.dataset.category=name; const sw=document.createElement('span'); sw.className='swatch'; sw.style.background=colorOf(name); const txt=document.createElement('span'); txt.textContent=name; el.appendChild(sw); el.appendChild(txt); items.appendChild(el); });78      }79 80      function render(rows){81        const { inner } = updateSize();82        const categories = Array.from(new Set(rows.map(r => r.category || 'Unknown'))).sort();83        const getColors = (n) => { try { if (window.ColorPalettes && typeof window.ColorPalettes.getColors==='function') return window.ColorPalettes.getColors('categorical', n); } catch(_){} return (window.d3 && d3.schemeTableau10) ? d3.schemeTableau10.slice(0, n) : ['#4e79a7','#f28e2b','#e15759','#76b7b2','#59a14f','#edc948','#b07aa1','#ff9da7','#9c755f','#bab0ab'].slice(0,n); };84        const palette = getColors(categories.length);85        const color = d3.scaleOrdinal().domain(categories).range(palette);86        const colorOf = (c) => color(c || 'Unknown');87 88        makeLegend(categories, colorOf);89 90        const totals = new Map(); categories.forEach(c => totals.set(c, 0)); rows.forEach(r => totals.set(r.category, (totals.get(r.category)||0) + (r.value||0)));91        const values = categories.map(c => ({ category:c, value: totals.get(c)||0 })).filter(d => d.value > 0);92        const sum = d3.sum(values, d=>d.value) || 1;93 94        const radius = Math.max(60, Math.min(inner, 120));95        const innerR = Math.round(radius * DONUT_INNER_RATIO);96        const pie = d3.pie().sort(null).value(d=>d.value).padAngle(0.02);97        const arc = d3.arc().innerRadius(innerR).outerRadius(radius).cornerRadius(3);98        const arcLabel = d3.arc().innerRadius((innerR + radius)/2).outerRadius((innerR + radius)/2);99 100        const data = pie(values);101        const slices = gRoot.selectAll('path.slice').data(data, d=>d.data.category);102        slices.enter().append('path').attr('class','slice')103          .attr('fill', d=>colorOf(d.data.category))104          .attr('stroke','var(--surface-bg)')105          .attr('stroke-width',1)106          .attr('data-category', d => d.data.category)107          .on('mouseenter', (ev, d) => {108            const pct = (d.data.value / sum) * 100;109            container.classList.add('hovering');110            gRoot.selectAll('path.slice').classed('ghost', s => (s && s.data && s.data.category) !== d.data.category);111            try { const items = legend.querySelectorAll('.item'); items.forEach(it => it.classList.toggle('ghost', it.dataset.category !== d.data.category)); } catch(_) {}112            const colorSw = colorOf(d.data.category);113            tipInner.innerHTML = `<div style="display:flex;align-items:center;gap:8px;white-space:nowrap;"><span class=\"swatch\" style=\"background:${colorSw}\"></span><strong>${d.data.category}</strong></div>` +114              `<div>Value</div>` +115              `<div style=\"display:flex;align-items:center;gap:6px;white-space:nowrap;\"><strong>Total</strong><span style=\"margin-left:auto;text-align:right;\">${d.data.value.toLocaleString()} (${pct.toFixed(1)}%)</span></div>`;116            tip.style.opacity='1';117          })118          .on('mousemove', (ev) => { const [mx,my] = d3.pointer(ev, container); tip.style.transform = `translate(${Math.round(mx+12)}px, ${Math.round(my+12)}px)`; })119          .on('mouseleave', () => {120            tip.style.opacity='0'; tip.style.transform='translate(-9999px, -9999px)';121            container.classList.remove('hovering');122            gRoot.selectAll('path.slice').classed('ghost', false);123            try { const items = legend.querySelectorAll('.item'); items.forEach(it => it.classList.remove('ghost')); } catch(_) {}124          })125          .merge(slices)126          .attr('d', arc)127          .attr('fill', d=>colorOf(d.data.category));128        slices.exit().remove();129 130        const labels = gRoot.selectAll('text.slice-label').data(data.filter(d => (d.data.value/sum) >= 0.03), d=>d.data.category);131        labels.enter().append('text').attr('class','slice-label').attr('text-anchor','middle')132          .merge(labels)133          .attr('transform', d => `translate(${arcLabel.centroid(d)})`)134          .text(d => `${((d.data.value/sum)*100).toFixed(1)}%`);135        labels.exit().remove();136      }137 138      (async () => {139        try {140          const text = await fetchFirstAvailable([DEFAULT_CSV, './assets/data/vision.csv', '../assets/data/vision.csv']);141          const rows = parseCsv(text);142          render(rows);143          const rerender = () => render(rows);144          if (window.ResizeObserver) { const ro = new ResizeObserver(() => rerender()); ro.observe(container); } else { window.addEventListener('resize', rerender); }145        } catch (e) {146          const pre = document.createElement('pre'); pre.textContent = (e && e.message) ? e.message : String(e); pre.style.color='var(--danger, #b00020)'; pre.style.fontSize='12px'; pre.style.whiteSpace='pre-wrap'; container.appendChild(pre);147        }148      })();149    };150 151    if (document.readyState==='loading'){ document.addEventListener('DOMContentLoaded', () => ensureD3(bootstrap), { once:true }); } else { ensureD3(bootstrap); }152  })();153</script>154 155