/**
* LossChart & ProgressRing Components
*
* SVG-based visualizations for training metrics:
* - LossChart: Line chart for training/validation loss curves
* - ProgressRing: Circular progress indicator
*/
'use client'
import type { TrainingDataPoint } from './training-metrics-types'
/**
* Simple line chart component for loss visualization
*/
export function LossChart({
data,
height = 200,
className = ''
}: {
data: TrainingDataPoint[]
height?: number
className?: string
}) {
if (data.length === 0) {
return (
Keine Daten
)
}
// Calculate bounds
const losses = data.map(d => d.loss)
const valLosses = data.filter(d => d.val_loss !== undefined).map(d => d.val_loss!)
const allLosses = [...losses, ...valLosses]
const minLoss = Math.min(...allLosses) * 0.9
const maxLoss = Math.max(...allLosses) * 1.1
const lossRange = maxLoss - minLoss || 1
// SVG dimensions
const width = 400
const padding = { top: 20, right: 20, bottom: 30, left: 50 }
const chartWidth = width - padding.left - padding.right
const chartHeight = height - padding.top - padding.bottom
// Generate path for loss line
const generatePath = (values: number[]) => {
if (values.length === 0) return ''
return values
.map((loss, idx) => {
const x = padding.left + (idx / (values.length - 1 || 1)) * chartWidth
const y = padding.top + chartHeight - ((loss - minLoss) / lossRange) * chartHeight
return `${idx === 0 ? 'M' : 'L'} ${x} ${y}`
})
.join(' ')
}
const lossPath = generatePath(losses)
const valLossPath = valLosses.length > 0 ? generatePath(valLosses) : ''
// Y-axis labels
const yLabels = [maxLoss, (maxLoss + minLoss) / 2, minLoss].map(v => v.toFixed(3))
return (
)
}
/**
* Progress ring component
*/
export function ProgressRing({
progress,
size = 80,
strokeWidth = 6,
className = ''
}: {
progress: number
size?: number
strokeWidth?: number
className?: string
}) {
const radius = (size - strokeWidth) / 2
const circumference = radius * 2 * Math.PI
const offset = circumference - (progress / 100) * circumference
return (
)
}