Spaces:
Configuration error
Configuration error
| <div class="d3-ml-metrics"></div> | |
| <style> | |
| .d3-ml-metrics { | |
| font-family: -apple-system, BlinkMacSystemFont, sans-serif; | |
| position: relative; | |
| } | |
| .d3-ml-metrics .chart-container { | |
| width: 100%; | |
| overflow: visible; | |
| } | |
| .d3-ml-metrics .axis path, | |
| .d3-ml-metrics .axis line { | |
| stroke: var(--axis-color); | |
| shape-rendering: crispEdges; | |
| } | |
| .d3-ml-metrics .axis text { | |
| fill: var(--tick-color); | |
| font-size: 11px; | |
| } | |
| .d3-ml-metrics .grid line { | |
| stroke: var(--grid-color); | |
| stroke-dasharray: 2,2; | |
| shape-rendering: crispEdges; | |
| } | |
| .d3-ml-metrics .line { | |
| fill: none; | |
| stroke-width: 2px; | |
| stroke-linejoin: round; | |
| stroke-linecap: round; | |
| } | |
| .d3-ml-metrics .legend { | |
| display: flex; | |
| flex-direction: column; | |
| gap: 8px; | |
| margin-top: 16px; | |
| } | |
| .d3-ml-metrics .legend-title { | |
| font-size: 12px; | |
| font-weight: 700; | |
| color: var(--text-color); | |
| } | |
| .d3-ml-metrics .legend-items { | |
| display: flex; | |
| flex-wrap: wrap; | |
| gap: 12px; | |
| } | |
| .d3-ml-metrics .legend-item { | |
| display: flex; | |
| align-items: center; | |
| gap: 6px; | |
| cursor: pointer; | |
| } | |
| .d3-ml-metrics .legend-swatch { | |
| width: 14px; | |
| height: 14px; | |
| border-radius: 3px; | |
| border: 1px solid var(--border-color); | |
| } | |
| .d3-ml-metrics .controls { | |
| display: flex; | |
| justify-content: flex-end; | |
| margin-bottom: 16px; | |
| } | |
| .d3-ml-metrics .control-group { | |
| display: flex; | |
| flex-direction: column; | |
| gap: 6px; | |
| } | |
| .d3-ml-metrics .control-group label { | |
| font-size: 12px; | |
| font-weight: 700; | |
| color: var(--text-color); | |
| } | |
| .d3-ml-metrics .control-group select { | |
| font-size: 12px; | |
| padding: 6px 24px 6px 8px; | |
| border: 1px solid var(--border-color); | |
| border-radius: 6px; | |
| background: var(--surface-bg); | |
| color: var(--text-color); | |
| appearance: none; | |
| background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3e%3cpolyline points='6 9 12 15 18 9'%3e%3c/polyline%3e%3c/svg%3e"); | |
| background-repeat: no-repeat; | |
| background-position: right 8px center; | |
| background-size: 14px; | |
| } | |
| .d3-ml-metrics .d3-tooltip { | |
| position: absolute; | |
| padding: 8px 12px; | |
| background: var(--surface-bg); | |
| border: 1px solid var(--border-color); | |
| border-radius: 6px; | |
| pointer-events: none; | |
| font-size: 12px; | |
| box-shadow: 0 4px 12px rgba(0,0,0,0.1); | |
| opacity: 0; | |
| transition: opacity 0.2s; | |
| } | |
| .d3-ml-metrics .d3-tooltip strong { | |
| display: block; | |
| margin-bottom: 4px; | |
| } | |
| </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 scriptEl = document.currentScript; | |
| let container = scriptEl ? scriptEl.previousElementSibling : null; | |
| if (!(container && container.classList && container.classList.contains('d3-ml-metrics'))) { | |
| const candidates = Array.from(document.querySelectorAll('.d3-ml-metrics')) | |
| .filter((el) => !(el.dataset && el.dataset.mounted === 'true')); | |
| container = candidates[candidates.length - 1] || null; | |
| } | |
| if (!container) return; | |
| if (container.dataset) { | |
| if (container.dataset.mounted === 'true') return; | |
| container.dataset.mounted = 'true'; | |
| } | |
| // Create UI elements | |
| const controls = document.createElement('div'); | |
| controls.className = 'controls'; | |
| const controlGroup = document.createElement('div'); | |
| controlGroup.className = 'control-group'; | |
| const label = document.createElement('label'); | |
| label.htmlFor = 'metric-select'; | |
| label.textContent = 'Metric'; | |
| const select = document.createElement('select'); | |
| select.id = 'metric-select'; | |
| controlGroup.appendChild(label); | |
| controlGroup.appendChild(select); | |
| controls.appendChild(controlGroup); | |
| container.appendChild(controls); | |
| // Create chart elements | |
| const svg = d3.select(container).append('svg') | |
| .attr('class', 'chart-container') | |
| .attr('width', '100%') | |
| .style('display', 'block'); | |
| const g = svg.append('g') | |
| .attr('class', 'chart-content'); | |
| // Create tooltip | |
| const tooltip = document.createElement('div'); | |
| tooltip.className = 'd3-tooltip'; | |
| container.appendChild(tooltip); | |
| // Sample ML metrics data | |
| const metricsData = [ | |
| { epoch: 1, accuracy: 0.65, loss: 1.2, val_accuracy: 0.62, val_loss: 1.3 }, | |
| { epoch: 2, accuracy: 0.72, loss: 0.9, val_accuracy: 0.68, val_loss: 1.1 }, | |
| { epoch: 3, accuracy: 0.78, loss: 0.7, val_accuracy: 0.74, val_loss: 0.95 }, | |
| { epoch: 4, accuracy: 0.82, loss: 0.6, val_accuracy: 0.79, val_loss: 0.8 }, | |
| { epoch: 5, accuracy: 0.85, loss: 0.5, val_accuracy: 0.82, val_loss: 0.7 }, | |
| { epoch: 6, accuracy: 0.88, loss: 0.45, val_accuracy: 0.84, val_loss: 0.65 }, | |
| { epoch: 7, accuracy: 0.89, loss: 0.4, val_accuracy: 0.86, val_loss: 0.6 }, | |
| { epoch: 8, accuracy: 0.91, loss: 0.35, val_accuracy: 0.87, val_loss: 0.55 }, | |
| { epoch: 9, accuracy: 0.92, loss: 0.3, val_accuracy: 0.88, val_loss: 0.5 }, | |
| { epoch: 10, accuracy: 0.93, loss: 0.25, val_accuracy: 0.89, val_loss: 0.45 } | |
| ]; | |
| // Available metrics | |
| const metrics = [ | |
| { id: 'accuracy', name: 'Accuracy', color: '#4e79a7' }, | |
| { id: 'loss', name: 'Loss', color: '#e15759' }, | |
| { id: 'val_accuracy', name: 'Val Accuracy', color: '#76b7b2' }, | |
| { id: 'val_loss', name: 'Val Loss', color: '#f28e2b' } | |
| ]; | |
| // Populate select | |
| metrics.forEach(metric => { | |
| const option = document.createElement('option'); | |
| option.value = metric.id; | |
| option.textContent = metric.name; | |
| select.appendChild(option); | |
| }); | |
| // Initial selection | |
| select.value = 'accuracy'; | |
| // Create legend | |
| const legend = document.createElement('div'); | |
| legend.className = 'legend'; | |
| const legendTitle = document.createElement('div'); | |
| legendTitle.className = 'legend-title'; | |
| legendTitle.textContent = 'Legend'; | |
| const legendItems = document.createElement('div'); | |
| legendItems.className = 'legend-items'; | |
| metrics.forEach(metric => { | |
| const item = document.createElement('div'); | |
| item.className = 'legend-item'; | |
| item.dataset.metric = metric.id; | |
| item.addEventListener('click', () => { | |
| select.value = metric.id; | |
| render(); | |
| }); | |
| const swatch = document.createElement('div'); | |
| swatch.className = 'legend-swatch'; | |
| swatch.style.backgroundColor = metric.color; | |
| const name = document.createElement('span'); | |
| name.textContent = metric.name; | |
| item.appendChild(swatch); | |
| item.appendChild(name); | |
| legendItems.appendChild(item); | |
| }); | |
| legend.appendChild(legendTitle); | |
| legend.appendChild(legendItems); | |
| container.appendChild(legend); | |
| // Chart dimensions and margins | |
| let width, height; | |
| const margin = { top: 20, right: 30, bottom: 40, left: 50 }; | |
| // Scales | |
| const x = d3.scaleLinear(); | |
| const y = d3.scaleLinear(); | |
| // Line generator | |
| const line = d3.line() | |
| .x(d => x(d.epoch)) | |
| .y(d => y(d[select.value])); | |
| // Update dimensions | |
| function updateDimensions() { | |
| width = container.clientWidth; | |
| height = Math.max(300, width * 0.5); | |
| svg.attr('height', height); | |
| x.range([margin.left, width - margin.right]) | |
| .domain([1, d3.max(metricsData, d => d.epoch)]); | |
| const selectedMetric = metrics.find(m => m.id === select.value); | |
| y.range([height - margin.bottom, margin.top]) | |
| .domain([0, d3.max(metricsData, d => d[select.value]) * 1.1]); | |
| } | |
| // Render chart | |
| function render() { | |
| updateDimensions(); | |
| const selectedMetric = metrics.find(m => m.id === select.value); | |
| // Clear previous elements | |
| g.selectAll('*').remove(); | |
| // Add grid | |
| g.append('g') | |
| .attr('class', 'grid') | |
| .attr('transform', `translate(0,${height - margin.bottom})`) | |
| .call(d3.axisBottom(x) | |
| .tickSize(-(height - margin.top - margin.bottom)) | |
| .tickFormat('') | |
| ); | |
| g.append('g') | |
| .attr('class', 'grid') | |
| .attr('transform', `translate(${margin.left},0)`) | |
| .call(d3.axisLeft(y) | |
| .tickSize(-(width - margin.left - margin.right)) | |
| .tickFormat('') | |
| ); | |
| // Add axes | |
| g.append('g') | |
| .attr('class', 'axis x-axis') | |
| .attr('transform', `translate(0,${height - margin.bottom})`) | |
| .call(d3.axisBottom(x)); | |
| g.append('g') | |
| .attr('class', 'axis y-axis') | |
| .attr('transform', `translate(${margin.left},0)`) | |
| .call(d3.axisLeft(y)); | |
| // Add line | |
| g.append('path') | |
| .datum(metricsData) | |
| .attr('class', 'line') | |
| .attr('stroke', selectedMetric.color) | |
| .attr('d', line); | |
| // Add points | |
| g.selectAll('.point') | |
| .data(metricsData) | |
| .enter() | |
| .append('circle') | |
| .attr('class', 'point') | |
| .attr('cx', d => x(d.epoch)) | |
| .attr('cy', d => y(d[select.value])) | |
| .attr('r', 4) | |
| .attr('fill', selectedMetric.color) | |
| .on('mouseover', (event, d) => { | |
| tooltip.innerHTML = ` | |
| <strong>Epoch ${d.epoch}</strong> | |
| ${selectedMetric.name}: ${d[select.value].toFixed(3)} | |
| `; | |
| tooltip.style.left = `${event.pageX + 10}px`; | |
| tooltip.style.top = `${event.pageY - 10}px`; | |
| tooltip.style.opacity = 1; | |
| }) | |
| .on('mouseout', () => { | |
| tooltip.style.opacity = 0; | |
| }); | |
| } | |
| // Handle select change | |
| select.addEventListener('change', render); | |
| // Initial render | |
| render(); | |
| // Handle resize | |
| const ro = window.ResizeObserver ? new ResizeObserver(render) : null; | |
| if (ro) ro.observe(container); | |
| else window.addEventListener('resize', render); | |
| }; | |
| if (document.readyState === 'loading') { | |
| document.addEventListener('DOMContentLoaded', () => ensureD3(bootstrap), { once: true }); | |
| } else { | |
| ensureD3(bootstrap); | |
| } | |
| })(); | |
| </script> |