Files
Atomizer/atomizer-dashboard/frontend/src/components/ParameterImportanceChart.tsx
Antoine 75d7036193 feat: Enhance dashboard with charts, study report viewer, and pruning tracking
- Add ConvergencePlot component with running best, statistics, gradient fill
- Add ParameterImportanceChart with Pearson correlation analysis
- Add StudyReportViewer with KaTeX math rendering and full markdown support
- Update pruning endpoint to query Optuna database directly
- Add /report endpoint for STUDY_REPORT.md files
- Fix chart data transformation for single/multi-objective studies
- Update Protocol 13 documentation with new components
- Update generate-report skill with dashboard integration

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-02 22:01:49 -05:00

185 lines
6.2 KiB
TypeScript

/**
* Parameter Importance Chart
* Shows which design parameters have the most impact on objectives
* Uses correlation analysis between parameters and objective values
*/
import { useMemo } from 'react';
import { BarChart, Bar, XAxis, YAxis, Tooltip, ResponsiveContainer, Cell } from 'recharts';
interface Trial {
trial_number: number;
values: number[];
params: Record<string, number>;
}
interface DesignVariable {
name: string;
parameter?: string;
unit?: string;
}
interface ParameterImportanceChartProps {
trials: Trial[];
designVariables: DesignVariable[];
objectiveIndex?: number;
objectiveName?: string;
}
// Calculate Pearson correlation coefficient
function pearsonCorrelation(x: number[], y: number[]): number {
if (x.length !== y.length || x.length < 2) return 0;
const n = x.length;
const sumX = x.reduce((a, b) => a + b, 0);
const sumY = y.reduce((a, b) => a + b, 0);
const sumXY = x.reduce((acc, xi, i) => acc + xi * y[i], 0);
const sumX2 = x.reduce((acc, xi) => acc + xi * xi, 0);
const sumY2 = y.reduce((acc, yi) => acc + yi * yi, 0);
const numerator = n * sumXY - sumX * sumY;
const denominator = Math.sqrt((n * sumX2 - sumX * sumX) * (n * sumY2 - sumY * sumY));
if (denominator === 0) return 0;
return numerator / denominator;
}
export function ParameterImportanceChart({
trials,
designVariables,
objectiveIndex = 0,
objectiveName = 'Objective'
}: ParameterImportanceChartProps) {
const importanceData = useMemo(() => {
if (!trials || trials.length < 3 || !designVariables || designVariables.length === 0) {
return [];
}
// Extract objective values
const objectiveValues = trials
.filter(t => t.values && t.values.length > objectiveIndex)
.map(t => t.values[objectiveIndex]);
if (objectiveValues.length < 3) return [];
// Calculate correlation for each parameter
const importances = designVariables.map(dv => {
const paramName = dv.parameter || dv.name;
const paramValues = trials
.filter(t => t.params && paramName in t.params && t.values && t.values.length > objectiveIndex)
.map(t => t.params[paramName]);
const corrObjectiveValues = trials
.filter(t => t.params && paramName in t.params && t.values && t.values.length > objectiveIndex)
.map(t => t.values[objectiveIndex]);
if (paramValues.length < 3) return { name: dv.name, importance: 0, correlation: 0 };
const correlation = pearsonCorrelation(paramValues, corrObjectiveValues);
// Use absolute correlation as importance (sign indicates direction)
const importance = Math.abs(correlation);
return {
name: dv.name,
importance: importance * 100, // Convert to percentage
correlation,
direction: correlation > 0 ? 'positive' : 'negative'
};
});
// Sort by importance (descending)
return importances.sort((a, b) => b.importance - a.importance);
}, [trials, designVariables, objectiveIndex]);
if (importanceData.length === 0) {
return (
<div className="bg-dark-700 rounded-lg p-6 border border-dark-500 shadow-sm">
<h3 className="text-lg font-semibold mb-4 text-dark-100">Parameter Importance</h3>
<div className="h-64 flex items-center justify-center text-dark-400">
Need at least 3 trials for correlation analysis
</div>
</div>
);
}
// Color based on correlation direction
const getBarColor = (entry: typeof importanceData[0]) => {
if (entry.correlation > 0) {
// Positive correlation (increasing param increases objective) - red for minimization
return '#f87171';
} else {
// Negative correlation (increasing param decreases objective) - green for minimization
return '#34d399';
}
};
return (
<div className="bg-dark-700 rounded-lg p-6 border border-dark-500 shadow-sm">
<div className="mb-4">
<h3 className="text-lg font-semibold text-dark-100">Parameter Importance</h3>
<p className="text-sm text-dark-400 mt-1">
Correlation with {objectiveName} ({trials.length} trials)
</p>
</div>
<div className="h-72">
<ResponsiveContainer width="100%" height="100%">
<BarChart
data={importanceData}
layout="vertical"
margin={{ top: 5, right: 30, left: 120, bottom: 5 }}
>
<XAxis
type="number"
domain={[0, 100]}
tickFormatter={(v) => `${v.toFixed(0)}%`}
tick={{ fill: '#94a3b8', fontSize: 11 }}
stroke="#334155"
/>
<YAxis
type="category"
dataKey="name"
width={110}
tick={{ fill: '#94a3b8', fontSize: 11 }}
stroke="#334155"
/>
<Tooltip
formatter={(value: number, _name: string, props: any) => {
const corr = props.payload.correlation;
return [
`${value.toFixed(1)}% (r=${corr.toFixed(3)})`,
'Importance'
];
}}
contentStyle={{
backgroundColor: '#1e293b',
border: '1px solid #334155',
borderRadius: '8px',
boxShadow: '0 4px 6px rgba(0,0,0,0.3)'
}}
labelStyle={{ color: '#e2e8f0' }}
/>
<Bar dataKey="importance" radius={[0, 4, 4, 0]}>
{importanceData.map((entry, index) => (
<Cell key={index} fill={getBarColor(entry)} />
))}
</Bar>
</BarChart>
</ResponsiveContainer>
</div>
{/* Legend */}
<div className="flex gap-6 justify-center mt-4 text-sm border-t border-dark-500 pt-4">
<div className="flex items-center gap-2">
<div className="w-4 h-4 rounded" style={{ backgroundColor: '#34d399' }} />
<span className="text-dark-300">Negative correlation (helps minimize)</span>
</div>
<div className="flex items-center gap-2">
<div className="w-4 h-4 rounded" style={{ backgroundColor: '#f87171' }} />
<span className="text-dark-300">Positive correlation (hurts minimize)</span>
</div>
</div>
</div>
);
}