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>
This commit is contained in:
@@ -0,0 +1,184 @@
|
||||
/**
|
||||
* 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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user