/** * Pareto Front Plot - Protocol 13 * Visualizes Pareto-optimal solutions for multi-objective optimization */ import { useState } from 'react'; import { ScatterChart, Scatter, Line, XAxis, YAxis, CartesianGrid, Tooltip, Cell, ResponsiveContainer, Legend } from 'recharts'; interface ParetoTrial { trial_number: number; values: [number, number]; params: Record; constraint_satisfied?: boolean; } interface Objective { name: string; type: 'minimize' | 'maximize'; unit?: string; } interface ParetoPlotProps { paretoData: ParetoTrial[]; objectives: Objective[]; } type NormalizationMode = 'raw' | 'minmax' | 'zscore'; export function ParetoPlot({ paretoData, objectives }: ParetoPlotProps) { const [normMode, setNormMode] = useState('raw'); if (paretoData.length === 0) { return (

Pareto Front

No Pareto front data yet
); } // Extract raw values const rawData = paretoData.map(trial => ({ x: trial.values[0], y: trial.values[1], trial_number: trial.trial_number, feasible: trial.constraint_satisfied !== false })); // Calculate statistics for normalization const xValues = rawData.map(d => d.x); const yValues = rawData.map(d => d.y); const xMin = Math.min(...xValues); const xMax = Math.max(...xValues); const yMin = Math.min(...yValues); const yMax = Math.max(...yValues); const xMean = xValues.reduce((a, b) => a + b, 0) / xValues.length; const yMean = yValues.reduce((a, b) => a + b, 0) / yValues.length; const xStd = Math.sqrt(xValues.reduce((sum, val) => sum + Math.pow(val - xMean, 2), 0) / xValues.length); const yStd = Math.sqrt(yValues.reduce((sum, val) => sum + Math.pow(val - yMean, 2), 0) / yValues.length); // Normalize data based on selected mode const normalizeX = (val: number): number => { if (normMode === 'minmax') { return xMax === xMin ? 0.5 : (val - xMin) / (xMax - xMin); } else if (normMode === 'zscore') { return xStd === 0 ? 0 : (val - xMean) / xStd; } return val; // raw }; const normalizeY = (val: number): number => { if (normMode === 'minmax') { return yMax === yMin ? 0.5 : (val - yMin) / (yMax - yMin); } else if (normMode === 'zscore') { return yStd === 0 ? 0 : (val - yMean) / yStd; } return val; // raw }; // Transform data with normalization const data = rawData.map(d => ({ x: normalizeX(d.x), y: normalizeY(d.y), rawX: d.x, rawY: d.y, trial_number: d.trial_number, feasible: d.feasible })); // Sort data by x-coordinate for Pareto front line const sortedData = [...data].sort((a, b) => a.x - b.x); // Get objective labels with normalization indicator const normSuffix = normMode === 'minmax' ? ' [0-1]' : normMode === 'zscore' ? ' [z-score]' : ''; const xLabel = objectives[0] ? `${objectives[0].name}${objectives[0].unit ? ` (${objectives[0].unit})` : ''}${normSuffix}` : `Objective 1${normSuffix}`; const yLabel = objectives[1] ? `${objectives[1].name}${objectives[1].unit ? ` (${objectives[1].unit})` : ''}${normSuffix}` : `Objective 2${normSuffix}`; // Custom tooltip (always shows raw values) const CustomTooltip = ({ active, payload }: any) => { if (!active || !payload || payload.length === 0) return null; const point = payload[0].payload; return (
Trial #{point.trial_number}
{objectives[0]?.name || 'Obj 1'}: {point.rawX.toFixed(4)}
{objectives[1]?.name || 'Obj 2'}: {point.rawY.toFixed(4)}
{point.feasible ? '✓ Feasible' : '✗ Infeasible'}
); }; return (

Pareto Front ({paretoData.length} solutions)

{/* Normalization Toggle */}
} /> (
Feasible
Infeasible
)} /> {/* Pareto front line */} {data.map((entry, index) => ( ))}
); }