import { useMemo } from 'react'; import Plot from 'react-plotly.js'; import { TrendingUp, TrendingDown, Minus } from 'lucide-react'; interface Run { run_id: number; name: string; source: 'FEA' | 'NN'; trial_count: number; best_value: number | null; avg_value: number | null; first_trial: string | null; last_trial: string | null; } interface PlotlyRunComparisonProps { runs: Run[]; height?: number; } export function PlotlyRunComparison({ runs, height = 400 }: PlotlyRunComparisonProps) { const chartData = useMemo(() => { if (runs.length === 0) return null; // Separate FEA and NN runs const feaRuns = runs.filter(r => r.source === 'FEA'); const nnRuns = runs.filter(r => r.source === 'NN'); // Create bar chart for trial counts const trialCountData = { x: runs.map(r => r.name), y: runs.map(r => r.trial_count), type: 'bar' as const, name: 'Trial Count', marker: { color: runs.map(r => r.source === 'NN' ? 'rgba(147, 51, 234, 0.8)' : 'rgba(59, 130, 246, 0.8)'), line: { color: runs.map(r => r.source === 'NN' ? 'rgb(147, 51, 234)' : 'rgb(59, 130, 246)'), width: 1 } }, hovertemplate: '%{x}
Trials: %{y}' }; // Create line chart for best values const bestValueData = { x: runs.map(r => r.name), y: runs.map(r => r.best_value), type: 'scatter' as const, mode: 'lines+markers' as const, name: 'Best Value', yaxis: 'y2', line: { color: 'rgba(16, 185, 129, 1)', width: 2 }, marker: { size: 8, color: 'rgba(16, 185, 129, 1)' }, hovertemplate: '%{x}
Best: %{y:.4e}' }; return { trialCountData, bestValueData, feaRuns, nnRuns }; }, [runs]); // Calculate statistics const stats = useMemo(() => { if (runs.length === 0) return null; const totalTrials = runs.reduce((sum, r) => sum + r.trial_count, 0); const feaTrials = runs.filter(r => r.source === 'FEA').reduce((sum, r) => sum + r.trial_count, 0); const nnTrials = runs.filter(r => r.source === 'NN').reduce((sum, r) => sum + r.trial_count, 0); const bestValues = runs.map(r => r.best_value).filter((v): v is number => v !== null); const overallBest = bestValues.length > 0 ? Math.min(...bestValues) : null; // Calculate improvement from first FEA run to overall best const feaRuns = runs.filter(r => r.source === 'FEA'); const firstFEA = feaRuns.length > 0 ? feaRuns[0].best_value : null; const improvement = firstFEA && overallBest ? ((firstFEA - overallBest) / Math.abs(firstFEA)) * 100 : null; return { totalTrials, feaTrials, nnTrials, overallBest, improvement, totalRuns: runs.length, feaRuns: runs.filter(r => r.source === 'FEA').length, nnRuns: runs.filter(r => r.source === 'NN').length }; }, [runs]); if (!chartData || !stats) { return (
No run data available
); } return (
{/* Stats Summary */}
Total Runs
{stats.totalRuns}
Total Trials
{stats.totalTrials}
FEA Trials
{stats.feaTrials}
NN Trials
{stats.nnTrials}
Best Value
{stats.overallBest !== null ? stats.overallBest.toExponential(3) : 'N/A'}
Improvement
{stats.improvement !== null ? ( <> {stats.improvement > 0 ? : stats.improvement < 0 ? : } {Math.abs(stats.improvement).toFixed(1)}% ) : 'N/A'}
{/* Chart */} {/* Runs Table */}
{runs.map((run) => { // Calculate duration if times available let duration = '-'; if (run.first_trial && run.last_trial) { const start = new Date(run.first_trial); const end = new Date(run.last_trial); const diffMs = end.getTime() - start.getTime(); const diffMins = Math.round(diffMs / 60000); if (diffMins < 60) { duration = `${diffMins}m`; } else { const hours = Math.floor(diffMins / 60); const mins = diffMins % 60; duration = `${hours}h ${mins}m`; } } return ( ); })}
Run Name Source Trials Best Value Avg Value Duration
{run.name} {run.source} {run.trial_count} {run.best_value !== null ? run.best_value.toExponential(4) : '-'} {run.avg_value !== null ? run.avg_value.toExponential(4) : '-'} {duration}
); } export default PlotlyRunComparison;