lerobot/robot-learning-tutorial
508
1<script>2 import { onMount, onDestroy } from 'svelte';3 import { SVGManager } from './core/svg-manager.js';4 import { GridRenderer } from './core/grid-renderer.js';5 import { PathRenderer } from './core/path-renderer.js';6 import { InteractionManager } from './core/interaction-manager.js';7 import { ChartTransforms } from './utils/chart-transforms.js';8 import { trackioSampler } from '../core/adaptive-sampler.js';9 10 // Props - same as original ChartRenderer11 export let metricData = {};12 export let rawMetricData = {};13 export let colorForRun = (name) => '#999';14 export let variant = 'classic';15 export let logScaleX = false;16 export let smoothing = false;17 export let normalizeLoss = true;18 export let metricKey = '';19 export let titleText = '';20 export let hostEl = null;21 export let width = 800;22 export let height = 150;23 export let margin = { top: 10, right: 12, bottom: 46, left: 44 };24 export let onHover = null;25 export let onLeave = null;26 27 // Internal state28 let container;29 let svgManager;30 let gridRenderer;31 let pathRenderer;32 let interactionManager;33 let cleanup;34 35 // Sampling state36 let sampledData = {};37 let samplingInfo = {};38 let needsSampling = false;39 40 // Computed values41 $: innerHeight = height - margin.top - margin.bottom;42 43 // Reactive rendering when data or props change44 $: {45 if (container && svgManager) {46 // List all dependencies to trigger render when any change47 void metricData;48 void metricKey;49 void variant;50 void logScaleX;51 void normalizeLoss;52 void smoothing;53 render();54 }55 }56 57 /**58 * Initialize all managers and renderers59 */60 function initializeManagers() {61 if (!container) return;62 63 // Create SVG manager with configuration64 svgManager = new SVGManager(container, { width, height, margin });65 svgManager.ensureSvg();66 svgManager.initializeScales(logScaleX);67 68 // Create specialized renderers69 gridRenderer = new GridRenderer(svgManager);70 pathRenderer = new PathRenderer(svgManager);71 interactionManager = new InteractionManager(svgManager, pathRenderer);72 73 console.log('๐ Chart managers initialized');74 }75 76 /**77 * Apply adaptive sampling to large datasets78 */79 function applySampling() {80 // Check if any run has more than 400 points81 const runSizes = Object.keys(metricData).map(run => (metricData[run] || []).length);82 const maxSize = Math.max(0, ...runSizes);83 needsSampling = maxSize > 400;84 85 if (needsSampling) {86 console.log(`๐ฏ Large dataset detected (${maxSize} points), applying adaptive sampling`);87 const result = trackioSampler.sampleMetricData(metricData, 'smart');88 sampledData = result.sampledData;89 samplingInfo = result.samplingInfo;90 91 // Log sampling stats92 Object.keys(samplingInfo).forEach(run => {93 const info = samplingInfo[run];94 console.log(`๐ ${run}: ${info.originalLength} โ ${info.sampledLength} points (${(info.compressionRatio * 100).toFixed(1)}% retained)`);95 });96 } else {97 sampledData = metricData;98 samplingInfo = {};99 }100 }101 102 /**103 * Main render function - orchestrates all rendering104 */105 function render() {106 if (!svgManager) return;107 108 // Apply sampling if needed109 applySampling();110 111 // Use sampled data for rendering112 const dataToRender = needsSampling ? sampledData : metricData;113 114 // Validate and clean data115 const cleanedData = ChartTransforms.validateData(dataToRender);116 const processedData = ChartTransforms.processMetricData(cleanedData, metricKey, normalizeLoss);117 118 if (!processedData.hasData) {119 const { root } = svgManager.getGroups();120 root.style('display', 'none');121 return;122 }123 124 const { root } = svgManager.getGroups();125 root.style('display', null);126 127 // Update scales based on log scale setting128 svgManager.initializeScales(logScaleX);129 130 // Setup scales and domains131 const { stepIndex } = ChartTransforms.setupScales(svgManager, processedData, logScaleX);132 const normalizeY = ChartTransforms.createNormalizeFunction(processedData, normalizeLoss);133 134 // Update lineGen with normalization135 const { line: lineGen, y: yScale } = svgManager.getScales();136 lineGen.y(d => yScale(normalizeY(d.value)));137 138 // Update layout and render axes139 const { innerWidth, xTicksForced, yTicksForced } = svgManager.updateLayout(processedData.hoverSteps, logScaleX);140 141 // Render grid142 gridRenderer.renderGrid(xTicksForced, yTicksForced, processedData.hoverSteps, variant);143 144 // Render data series145 pathRenderer.renderSeries(146 processedData.runs, 147 cleanedData, 148 rawMetricData, 149 colorForRun, 150 smoothing, 151 logScaleX, 152 stepIndex, 153 normalizeY154 );155 156 // Setup interactions157 interactionManager.setupHoverInteractions(158 processedData.hoverSteps,159 stepIndex,160 processedData.runs.map(r => ({ 161 run: r, 162 color: colorForRun(r), 163 values: (cleanedData[r] || []).slice().sort((a, b) => a.step - b.step) 164 })),165 normalizeY,166 processedData.isAccuracy,167 innerWidth,168 logScaleX,169 onHover,170 onLeave171 );172 }173 174 /**175 * Public API: Show hover line at specific step176 */177 export function showHoverLine(step) {178 if (!interactionManager) return;179 180 // Use sampled data for interactions as well181 const dataToRender = needsSampling ? sampledData : metricData;182 const cleanedData = ChartTransforms.validateData(dataToRender);183 const processedData = ChartTransforms.processMetricData(cleanedData, metricKey, normalizeLoss);184 const { stepIndex } = ChartTransforms.setupScales(svgManager, processedData, logScaleX);185 186 interactionManager.showHoverLine(step, processedData.hoverSteps, stepIndex, logScaleX);187 }188 189 /**190 * Public API: Hide hover line191 */192 export function hideHoverLine() {193 if (interactionManager) {194 interactionManager.hideHoverLine();195 }196 }197 198 /**199 * Setup resize observer and lifecycle200 */201 onMount(() => {202 initializeManagers();203 render();204 205 // Debounced resize handling for better mobile performance206 let resizeTimeout;207 const debouncedRender = () => {208 if (resizeTimeout) clearTimeout(resizeTimeout);209 resizeTimeout = setTimeout(() => {210 render();211 }, 100);212 };213 214 const ro = window.ResizeObserver ? new ResizeObserver(debouncedRender) : null;215 if (ro && container) ro.observe(container);216 217 // Listen for orientation changes on mobile218 const handleOrientationChange = () => {219 setTimeout(() => {220 render();221 }, 300);222 };223 224 window.addEventListener('orientationchange', handleOrientationChange);225 window.addEventListener('resize', debouncedRender);226 227 cleanup = () => { 228 if (ro) ro.disconnect();229 if (resizeTimeout) clearTimeout(resizeTimeout);230 window.removeEventListener('orientationchange', handleOrientationChange);231 window.removeEventListener('resize', debouncedRender);232 if (svgManager) svgManager.destroy();233 if (interactionManager) interactionManager.destroy();234 };235 });236 237 onDestroy(() => {238 cleanup && cleanup();239 });240</script>241 242<div bind:this={container} style="width: 100%; height: 100%; min-width: 200px; overflow: hidden;"></div>243 