Files
Atomizer/atomizer-dashboard/frontend/src/components/dashboard/ParallelCoordinatesPlot.tsx
Anto01 e3bdb08a22 feat: Major update with validators, skills, dashboard, and docs reorganization
- Add validation framework (config, model, results, study validators)
- Add Claude Code skills (create-study, run-optimization, generate-report,
  troubleshoot, analyze-model)
- Add Atomizer Dashboard (React frontend + FastAPI backend)
- Reorganize docs into structured directories (00-09)
- Add neural surrogate modules and training infrastructure
- Add multi-objective optimization support

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-25 19:23:58 -05:00

139 lines
4.3 KiB
TypeScript

import { Card } from '../common/Card';
interface ParallelCoordinatesPlotProps {
data: any[];
dimensions: string[];
colorBy?: string;
}
export const ParallelCoordinatesPlot = ({ data, dimensions }: ParallelCoordinatesPlotProps) => {
// Filter out null/undefined data points
const validData = data.filter(d => d && dimensions.every(dim => d[dim] !== null && d[dim] !== undefined));
if (validData.length === 0 || dimensions.length === 0) {
return (
<Card title="Parallel Coordinates">
<div className="h-80 flex items-center justify-center text-dark-300">
No data available for parallel coordinates
</div>
</Card>
);
}
// Calculate min/max for each dimension for normalization
const ranges = dimensions.map(dim => {
const values = validData.map(d => d[dim]);
return {
min: Math.min(...values),
max: Math.max(...values)
};
});
// Normalize function
const normalize = (value: number, dimIdx: number): number => {
const range = ranges[dimIdx];
if (range.max === range.min) return 0.5;
return (value - range.min) / (range.max - range.min);
};
// Chart dimensions
const width = 800;
const height = 400;
const margin = { top: 80, right: 20, bottom: 40, left: 20 };
const plotWidth = width - margin.left - margin.right;
const plotHeight = height - margin.top - margin.bottom;
const axisSpacing = plotWidth / (dimensions.length - 1);
return (
<Card title={`Parallel Coordinates (${validData.length} solutions)`}>
<svg width={width} height={height} className="overflow-visible">
<g transform={`translate(${margin.left}, ${margin.top})`}>
{/* Draw axes */}
{dimensions.map((dim, i) => {
const x = i * axisSpacing;
return (
<g key={dim} transform={`translate(${x}, 0)`}>
{/* Axis line */}
<line
y1={0}
y2={plotHeight}
stroke="#475569"
strokeWidth={2}
/>
{/* Axis label */}
<text
y={-10}
textAnchor="middle"
fill="#94a3b8"
fontSize={12}
className="select-none"
transform={`rotate(-45, 0, -10)`}
>
{dim}
</text>
{/* Min/max labels */}
<text
y={plotHeight + 15}
textAnchor="middle"
fill="#64748b"
fontSize={10}
>
{ranges[i].min.toFixed(2)}
</text>
<text
y={-25}
textAnchor="middle"
fill="#64748b"
fontSize={10}
>
{ranges[i].max.toFixed(2)}
</text>
</g>
);
})}
{/* Draw lines for each trial */}
{validData.map((trial, trialIdx) => {
// Build path
const pathData = dimensions.map((dim, i) => {
const x = i * axisSpacing;
const normalizedY = normalize(trial[dim], i);
const y = plotHeight * (1 - normalizedY);
return i === 0 ? `M ${x} ${y}` : `L ${x} ${y}`;
}).join(' ');
return (
<path
key={trialIdx}
d={pathData}
fill="none"
stroke={trial.isPareto !== false ? '#10b981' : '#60a5fa'}
strokeWidth={1}
opacity={0.4}
strokeLinecap="round"
strokeLinejoin="round"
className="transition-all duration-200"
/>
);
})}
</g>
</svg>
{/* Legend */}
<div className="flex gap-6 justify-center mt-4 text-sm">
<div className="flex items-center gap-2">
<div className="w-8 h-0.5 bg-green-400" />
<span className="text-dark-200">Pareto Front</span>
</div>
<div className="flex items-center gap-2">
<div className="w-8 h-0.5 bg-blue-400" />
<span className="text-dark-200">Other Solutions</span>
</div>
</div>
</Card>
);
};