From f067497e08a155beee11f021a6d5be64991b1336 Mon Sep 17 00:00:00 2001 From: Anto01 Date: Tue, 20 Jan 2026 13:11:02 -0500 Subject: [PATCH] refactor(dashboard): Remove unused Plotly components Removed plotly/ directory with unused chart wrappers: - PlotlyConvergencePlot, PlotlyCorrelationHeatmap - PlotlyFeasibilityChart, PlotlyParallelCoordinates - PlotlyParameterImportance, PlotlyParetoPlot - PlotlyRunComparison, PlotlySurrogateQuality These were replaced by Recharts-based implementations. --- .../plotly/PlotlyConvergencePlot.tsx | 260 ---------- .../plotly/PlotlyCorrelationHeatmap.tsx | 161 ------- .../plotly/PlotlyFeasibilityChart.tsx | 120 ----- .../plotly/PlotlyParallelCoordinates.tsx | 221 --------- .../plotly/PlotlyParameterImportance.tsx | 209 -------- .../components/plotly/PlotlyParetoPlot.tsx | 448 ------------------ .../components/plotly/PlotlyRunComparison.tsx | 247 ---------- .../plotly/PlotlySurrogateQuality.tsx | 202 -------- .../frontend/src/components/plotly/README.md | 217 --------- .../frontend/src/components/plotly/index.ts | 15 - 10 files changed, 2100 deletions(-) delete mode 100644 atomizer-dashboard/frontend/src/components/plotly/PlotlyConvergencePlot.tsx delete mode 100644 atomizer-dashboard/frontend/src/components/plotly/PlotlyCorrelationHeatmap.tsx delete mode 100644 atomizer-dashboard/frontend/src/components/plotly/PlotlyFeasibilityChart.tsx delete mode 100644 atomizer-dashboard/frontend/src/components/plotly/PlotlyParallelCoordinates.tsx delete mode 100644 atomizer-dashboard/frontend/src/components/plotly/PlotlyParameterImportance.tsx delete mode 100644 atomizer-dashboard/frontend/src/components/plotly/PlotlyParetoPlot.tsx delete mode 100644 atomizer-dashboard/frontend/src/components/plotly/PlotlyRunComparison.tsx delete mode 100644 atomizer-dashboard/frontend/src/components/plotly/PlotlySurrogateQuality.tsx delete mode 100644 atomizer-dashboard/frontend/src/components/plotly/README.md delete mode 100644 atomizer-dashboard/frontend/src/components/plotly/index.ts diff --git a/atomizer-dashboard/frontend/src/components/plotly/PlotlyConvergencePlot.tsx b/atomizer-dashboard/frontend/src/components/plotly/PlotlyConvergencePlot.tsx deleted file mode 100644 index e36e13a8..00000000 --- a/atomizer-dashboard/frontend/src/components/plotly/PlotlyConvergencePlot.tsx +++ /dev/null @@ -1,260 +0,0 @@ -/** - * PlotlyConvergencePlot - Interactive convergence plot using Plotly - * - * Features: - * - Line plot showing objective vs trial number - * - Best-so-far trace overlay - * - FEA vs NN trial differentiation - * - Hover tooltips with trial details - * - Range slider for zooming - * - Log scale toggle - * - Export to PNG/SVG - */ - -import { useMemo, useState } from 'react'; -import Plot from 'react-plotly.js'; - -interface Trial { - trial_number: number; - values: number[]; - params: Record; - user_attrs?: Record; - source?: 'FEA' | 'NN' | 'V10_FEA'; - constraint_satisfied?: boolean; -} - -// Penalty threshold - objectives above this are considered failed/penalty trials -const PENALTY_THRESHOLD = 100000; - -interface PlotlyConvergencePlotProps { - trials: Trial[]; - objectiveIndex?: number; - objectiveName?: string; - direction?: 'minimize' | 'maximize'; - height?: number; - showRangeSlider?: boolean; - showLogScaleToggle?: boolean; -} - -export function PlotlyConvergencePlot({ - trials, - objectiveIndex = 0, - objectiveName = 'Objective', - direction = 'minimize', - height = 400, - showRangeSlider = true, - showLogScaleToggle = true -}: PlotlyConvergencePlotProps) { - const [useLogScale, setUseLogScale] = useState(false); - - // Process trials and calculate best-so-far - const { feaData, nnData, bestSoFar, allX, allY } = useMemo(() => { - if (!trials.length) return { feaData: { x: [], y: [], text: [] }, nnData: { x: [], y: [], text: [] }, bestSoFar: { x: [], y: [] }, allX: [], allY: [] }; - - // Sort by trial number - const sorted = [...trials].sort((a, b) => a.trial_number - b.trial_number); - - const fea: { x: number[]; y: number[]; text: string[] } = { x: [], y: [], text: [] }; - const nn: { x: number[]; y: number[]; text: string[] } = { x: [], y: [], text: [] }; - const best: { x: number[]; y: number[] } = { x: [], y: [] }; - const xs: number[] = []; - const ys: number[] = []; - - let bestValue = direction === 'minimize' ? Infinity : -Infinity; - - sorted.forEach(t => { - const val = t.values?.[objectiveIndex] ?? t.user_attrs?.[objectiveName] ?? null; - if (val === null || !isFinite(val)) return; - - // Filter out failed/penalty trials: - // 1. Objective above penalty threshold (e.g., 1000000 = solver failure) - // 2. constraint_satisfied explicitly false - // 3. user_attrs indicates pruned/failed - const isPenalty = val >= PENALTY_THRESHOLD; - const isFailed = t.constraint_satisfied === false; - const isPruned = t.user_attrs?.pruned === true || t.user_attrs?.fail_reason; - if (isPenalty || isFailed || isPruned) return; - - const source = t.source || t.user_attrs?.source || 'FEA'; - const hoverText = `Trial #${t.trial_number}
${objectiveName}: ${val.toFixed(4)}
Source: ${source}`; - - xs.push(t.trial_number); - ys.push(val); - - if (source === 'NN') { - nn.x.push(t.trial_number); - nn.y.push(val); - nn.text.push(hoverText); - } else { - fea.x.push(t.trial_number); - fea.y.push(val); - fea.text.push(hoverText); - } - - // Update best-so-far - if (direction === 'minimize') { - if (val < bestValue) bestValue = val; - } else { - if (val > bestValue) bestValue = val; - } - best.x.push(t.trial_number); - best.y.push(bestValue); - }); - - return { feaData: fea, nnData: nn, bestSoFar: best, allX: xs, allY: ys }; - }, [trials, objectiveIndex, objectiveName, direction]); - - if (!trials.length || allX.length === 0) { - return ( -
- No trial data available -
- ); - } - - const traces: any[] = []; - - // FEA trials scatter - if (feaData.x.length > 0) { - traces.push({ - type: 'scatter', - mode: 'markers', - name: `FEA (${feaData.x.length})`, - x: feaData.x, - y: feaData.y, - text: feaData.text, - hoverinfo: 'text', - marker: { - color: '#3B82F6', - size: 8, - opacity: 0.7, - line: { color: '#1E40AF', width: 1 } - } - }); - } - - // NN trials scatter - if (nnData.x.length > 0) { - traces.push({ - type: 'scatter', - mode: 'markers', - name: `NN (${nnData.x.length})`, - x: nnData.x, - y: nnData.y, - text: nnData.text, - hoverinfo: 'text', - marker: { - color: '#F97316', - size: 6, - symbol: 'cross', - opacity: 0.6 - } - }); - } - - // Best-so-far line - if (bestSoFar.x.length > 0) { - traces.push({ - type: 'scatter', - mode: 'lines', - name: 'Best So Far', - x: bestSoFar.x, - y: bestSoFar.y, - line: { - color: '#10B981', - width: 3, - shape: 'hv' // Step line - }, - hoverinfo: 'y' - }); - } - - const layout: any = { - height, - margin: { l: 60, r: 30, t: 30, b: showRangeSlider ? 80 : 50 }, - paper_bgcolor: 'rgba(0,0,0,0)', - plot_bgcolor: 'rgba(0,0,0,0)', - xaxis: { - title: 'Trial Number', - gridcolor: '#E5E7EB', - zerolinecolor: '#D1D5DB', - rangeslider: showRangeSlider ? { visible: true } : undefined - }, - yaxis: { - title: useLogScale ? `log₁₀(${objectiveName})` : objectiveName, - gridcolor: '#E5E7EB', - zerolinecolor: '#D1D5DB', - type: useLogScale ? 'log' : 'linear' - }, - legend: { - x: 1, - y: 1, - xanchor: 'right', - bgcolor: 'rgba(255,255,255,0.8)', - bordercolor: '#E5E7EB', - borderwidth: 1 - }, - font: { family: 'Inter, system-ui, sans-serif' }, - hovermode: 'closest' - }; - - // Best value annotation - const bestVal = direction === 'minimize' - ? Math.min(...allY) - : Math.max(...allY); - const bestIdx = allY.indexOf(bestVal); - const bestTrial = allX[bestIdx]; - - return ( -
- {/* Summary stats and controls */} -
-
-
- Best: {bestVal.toFixed(4)} - (Trial #{bestTrial}) -
-
- Current: {allY[allY.length - 1].toFixed(4)} -
-
- Trials: {allX.length} -
-
- - {/* Log scale toggle */} - {showLogScaleToggle && ( - - )} -
- - -
- ); -} diff --git a/atomizer-dashboard/frontend/src/components/plotly/PlotlyCorrelationHeatmap.tsx b/atomizer-dashboard/frontend/src/components/plotly/PlotlyCorrelationHeatmap.tsx deleted file mode 100644 index 58f129a5..00000000 --- a/atomizer-dashboard/frontend/src/components/plotly/PlotlyCorrelationHeatmap.tsx +++ /dev/null @@ -1,161 +0,0 @@ -import { useMemo } from 'react'; -import Plot from 'react-plotly.js'; - -interface TrialData { - trial_number: number; - values: number[]; - params: Record; -} - -interface PlotlyCorrelationHeatmapProps { - trials: TrialData[]; - objectiveName?: string; - height?: number; -} - -// Calculate Pearson correlation coefficient -function pearsonCorrelation(x: number[], y: number[]): number { - const n = x.length; - if (n === 0 || n !== y.length) return 0; - - const meanX = x.reduce((a, b) => a + b, 0) / n; - const meanY = y.reduce((a, b) => a + b, 0) / n; - - let numerator = 0; - let denomX = 0; - let denomY = 0; - - for (let i = 0; i < n; i++) { - const dx = x[i] - meanX; - const dy = y[i] - meanY; - numerator += dx * dy; - denomX += dx * dx; - denomY += dy * dy; - } - - const denominator = Math.sqrt(denomX) * Math.sqrt(denomY); - return denominator === 0 ? 0 : numerator / denominator; -} - -export function PlotlyCorrelationHeatmap({ - trials, - objectiveName = 'Objective', - height = 500 -}: PlotlyCorrelationHeatmapProps) { - const { matrix, labels, annotations } = useMemo(() => { - if (trials.length < 3) { - return { matrix: [], labels: [], annotations: [] }; - } - - // Get parameter names - const paramNames = Object.keys(trials[0].params); - const allLabels = [...paramNames, objectiveName]; - - // Extract data columns - const columns: Record = {}; - paramNames.forEach(name => { - columns[name] = trials.map(t => t.params[name]).filter(v => v !== undefined && !isNaN(v)); - }); - columns[objectiveName] = trials.map(t => t.values[0]).filter(v => v !== undefined && !isNaN(v)); - - // Calculate correlation matrix - const n = allLabels.length; - const correlationMatrix: number[][] = []; - const annotationData: any[] = []; - - for (let i = 0; i < n; i++) { - const row: number[] = []; - for (let j = 0; j < n; j++) { - const col1 = columns[allLabels[i]]; - const col2 = columns[allLabels[j]]; - - // Ensure same length - const minLen = Math.min(col1.length, col2.length); - const corr = pearsonCorrelation(col1.slice(0, minLen), col2.slice(0, minLen)); - row.push(corr); - - // Add annotation - annotationData.push({ - x: allLabels[j], - y: allLabels[i], - text: corr.toFixed(2), - showarrow: false, - font: { - color: Math.abs(corr) > 0.5 ? '#fff' : '#888', - size: 11 - } - }); - } - correlationMatrix.push(row); - } - - return { - matrix: correlationMatrix, - labels: allLabels, - annotations: annotationData - }; - }, [trials, objectiveName]); - - if (trials.length < 3) { - return ( -
-

Need at least 3 trials to compute correlations

-
- ); - } - - return ( - Correlation: %{z:.3f}' - } - ]} - layout={{ - title: { - text: 'Parameter-Objective Correlation Matrix', - font: { color: '#fff', size: 14 } - }, - height, - margin: { l: 120, r: 60, t: 60, b: 120 }, - paper_bgcolor: 'transparent', - plot_bgcolor: 'transparent', - xaxis: { - tickangle: 45, - tickfont: { color: '#888', size: 10 }, - gridcolor: 'rgba(255,255,255,0.05)' - }, - yaxis: { - tickfont: { color: '#888', size: 10 }, - gridcolor: 'rgba(255,255,255,0.05)' - }, - annotations: annotations - }} - config={{ - displayModeBar: true, - modeBarButtonsToRemove: ['lasso2d', 'select2d'], - displaylogo: false - }} - style={{ width: '100%' }} - /> - ); -} diff --git a/atomizer-dashboard/frontend/src/components/plotly/PlotlyFeasibilityChart.tsx b/atomizer-dashboard/frontend/src/components/plotly/PlotlyFeasibilityChart.tsx deleted file mode 100644 index f49cc43f..00000000 --- a/atomizer-dashboard/frontend/src/components/plotly/PlotlyFeasibilityChart.tsx +++ /dev/null @@ -1,120 +0,0 @@ -import { useMemo } from 'react'; -import Plot from 'react-plotly.js'; - -interface TrialData { - trial_number: number; - values: number[]; - constraint_satisfied?: boolean; -} - -interface PlotlyFeasibilityChartProps { - trials: TrialData[]; - height?: number; -} - -export function PlotlyFeasibilityChart({ - trials, - height = 350 -}: PlotlyFeasibilityChartProps) { - const { trialNumbers, cumulativeFeasibility, windowedFeasibility } = useMemo(() => { - if (trials.length === 0) { - return { trialNumbers: [], cumulativeFeasibility: [], windowedFeasibility: [] }; - } - - // Sort trials by number - const sorted = [...trials].sort((a, b) => a.trial_number - b.trial_number); - - const numbers: number[] = []; - const cumulative: number[] = []; - const windowed: number[] = []; - - let feasibleCount = 0; - const windowSize = Math.min(20, Math.floor(sorted.length / 5) || 1); - - sorted.forEach((trial, idx) => { - numbers.push(trial.trial_number); - - // Cumulative feasibility - if (trial.constraint_satisfied !== false) { - feasibleCount++; - } - cumulative.push((feasibleCount / (idx + 1)) * 100); - - // Windowed (rolling) feasibility - const windowStart = Math.max(0, idx - windowSize + 1); - const windowTrials = sorted.slice(windowStart, idx + 1); - const windowFeasible = windowTrials.filter(t => t.constraint_satisfied !== false).length; - windowed.push((windowFeasible / windowTrials.length) * 100); - }); - - return { trialNumbers: numbers, cumulativeFeasibility: cumulative, windowedFeasibility: windowed }; - }, [trials]); - - if (trials.length === 0) { - return ( -
-

No trials to display

-
- ); - } - - return ( - Cumulative: %{y:.1f}%' - }, - { - x: trialNumbers, - y: windowedFeasibility, - type: 'scatter', - mode: 'lines', - name: 'Rolling (20-trial)', - line: { color: '#60a5fa', width: 2, dash: 'dot' }, - hovertemplate: 'Trial %{x}
Rolling: %{y:.1f}%' - } - ]} - layout={{ - height, - margin: { l: 60, r: 30, t: 30, b: 50 }, - paper_bgcolor: 'transparent', - plot_bgcolor: 'transparent', - xaxis: { - title: { text: 'Trial Number', font: { color: '#888' } }, - tickfont: { color: '#888' }, - gridcolor: 'rgba(255,255,255,0.05)', - zeroline: false - }, - yaxis: { - title: { text: 'Feasibility Rate (%)', font: { color: '#888' } }, - tickfont: { color: '#888' }, - gridcolor: 'rgba(255,255,255,0.1)', - zeroline: false, - range: [0, 105] - }, - legend: { - font: { color: '#888' }, - bgcolor: 'rgba(0,0,0,0.5)', - x: 0.02, - y: 0.98, - xanchor: 'left', - yanchor: 'top' - }, - showlegend: true, - hovermode: 'x unified' - }} - config={{ - displayModeBar: true, - modeBarButtonsToRemove: ['lasso2d', 'select2d'], - displaylogo: false - }} - style={{ width: '100%' }} - /> - ); -} diff --git a/atomizer-dashboard/frontend/src/components/plotly/PlotlyParallelCoordinates.tsx b/atomizer-dashboard/frontend/src/components/plotly/PlotlyParallelCoordinates.tsx deleted file mode 100644 index c33e573b..00000000 --- a/atomizer-dashboard/frontend/src/components/plotly/PlotlyParallelCoordinates.tsx +++ /dev/null @@ -1,221 +0,0 @@ -/** - * PlotlyParallelCoordinates - Interactive parallel coordinates plot using Plotly - * - * Features: - * - Native zoom, pan, and selection - * - Hover tooltips with trial details - * - Brush filtering on each axis - * - FEA vs NN color differentiation - * - Export to PNG/SVG - */ - -import { useMemo } from 'react'; -import Plot from 'react-plotly.js'; - -interface Trial { - trial_number: number; - values: number[]; - params: Record; - user_attrs?: Record; - constraint_satisfied?: boolean; - source?: 'FEA' | 'NN' | 'V10_FEA'; -} - -interface Objective { - name: string; - direction?: 'minimize' | 'maximize'; - unit?: string; -} - -interface DesignVariable { - name: string; - unit?: string; - min?: number; - max?: number; -} - -interface PlotlyParallelCoordinatesProps { - trials: Trial[]; - objectives: Objective[]; - designVariables: DesignVariable[]; - paretoFront?: Trial[]; - height?: number; -} - -export function PlotlyParallelCoordinates({ - trials, - objectives, - designVariables, - paretoFront = [], - height = 500 -}: PlotlyParallelCoordinatesProps) { - // Create set of Pareto front trial numbers - const paretoSet = useMemo(() => new Set(paretoFront.map(t => t.trial_number)), [paretoFront]); - - // Build dimensions array for parallel coordinates - const { dimensions, colorValues, colorScale } = useMemo(() => { - if (!trials.length) return { dimensions: [], colorValues: [], colorScale: [] }; - - const dims: any[] = []; - const colors: number[] = []; - - // Get all design variable names - const dvNames = designVariables.map(dv => dv.name); - const objNames = objectives.map(obj => obj.name); - - // Add design variable dimensions - dvNames.forEach((name, idx) => { - const dv = designVariables[idx]; - const values = trials.map(t => t.params[name] ?? 0); - const validValues = values.filter(v => v !== null && v !== undefined && isFinite(v)); - - if (validValues.length === 0) return; - - dims.push({ - label: name, - values: values, - range: [ - dv?.min ?? Math.min(...validValues), - dv?.max ?? Math.max(...validValues) - ], - constraintrange: undefined - }); - }); - - // Add objective dimensions - objNames.forEach((name, idx) => { - const obj = objectives[idx]; - const values = trials.map(t => { - // Try to get from values array first, then user_attrs - if (t.values && t.values[idx] !== undefined) { - return t.values[idx]; - } - return t.user_attrs?.[name] ?? 0; - }); - const validValues = values.filter(v => v !== null && v !== undefined && isFinite(v)); - - if (validValues.length === 0) return; - - dims.push({ - label: `${name}${obj.unit ? ` (${obj.unit})` : ''}`, - values: values, - range: [Math.min(...validValues) * 0.95, Math.max(...validValues) * 1.05] - }); - }); - - // Build color array: 0 = V10_FEA, 1 = FEA, 2 = NN, 3 = Pareto - trials.forEach(t => { - const source = t.source || t.user_attrs?.source || 'FEA'; - const isPareto = paretoSet.has(t.trial_number); - - if (isPareto) { - colors.push(3); // Pareto - special color - } else if (source === 'NN') { - colors.push(2); // NN trials - } else if (source === 'V10_FEA') { - colors.push(0); // V10 FEA - } else { - colors.push(1); // V11 FEA - } - }); - - // Color scale: V10_FEA (light blue), FEA (blue), NN (orange), Pareto (green) - const scale: [number, string][] = [ - [0, '#93C5FD'], // V10_FEA - light blue - [0.33, '#2563EB'], // FEA - blue - [0.66, '#F97316'], // NN - orange - [1, '#10B981'] // Pareto - green - ]; - - return { dimensions: dims, colorValues: colors, colorScale: scale }; - }, [trials, objectives, designVariables, paretoSet]); - - if (!trials.length || dimensions.length === 0) { - return ( -
- No trial data available for parallel coordinates -
- ); - } - - // Count trial types for legend - const feaCount = trials.filter(t => { - const source = t.source || t.user_attrs?.source || 'FEA'; - return source === 'FEA' || source === 'V10_FEA'; - }).length; - const nnCount = trials.filter(t => { - const source = t.source || t.user_attrs?.source || 'FEA'; - return source === 'NN'; - }).length; - - return ( -
- {/* Legend */} -
-
-
- FEA ({feaCount}) -
-
-
- NN ({nnCount}) -
- {paretoFront.length > 0 && ( -
-
- Pareto ({paretoFront.length}) -
- )} -
- - - -

- Drag along axes to filter. Double-click to reset. -

-
- ); -} diff --git a/atomizer-dashboard/frontend/src/components/plotly/PlotlyParameterImportance.tsx b/atomizer-dashboard/frontend/src/components/plotly/PlotlyParameterImportance.tsx deleted file mode 100644 index 384f115f..00000000 --- a/atomizer-dashboard/frontend/src/components/plotly/PlotlyParameterImportance.tsx +++ /dev/null @@ -1,209 +0,0 @@ -/** - * PlotlyParameterImportance - Interactive parameter importance chart using Plotly - * - * Features: - * - Horizontal bar chart showing correlation/importance - * - Color coding by positive/negative correlation - * - Hover tooltips with details - * - Sortable by importance - */ - -import { useMemo, useState } from 'react'; -import Plot from 'react-plotly.js'; - -interface Trial { - trial_number: number; - values: number[]; - params: Record; - user_attrs?: Record; -} - -interface DesignVariable { - name: string; - unit?: string; -} - -interface PlotlyParameterImportanceProps { - trials: Trial[]; - designVariables: DesignVariable[]; - objectiveIndex?: number; - objectiveName?: string; - height?: number; -} - -// Calculate Pearson correlation coefficient -function pearsonCorrelation(x: number[], y: number[]): number { - const n = x.length; - if (n === 0) return 0; - - 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 PlotlyParameterImportance({ - trials, - designVariables, - objectiveIndex = 0, - objectiveName = 'Objective', - height = 400 -}: PlotlyParameterImportanceProps) { - const [sortBy, setSortBy] = useState<'importance' | 'name'>('importance'); - - // Calculate correlations for each parameter - const correlations = useMemo(() => { - if (!trials.length || !designVariables.length) return []; - - // Get objective values - const objValues = trials.map(t => { - if (t.values && t.values[objectiveIndex] !== undefined) { - return t.values[objectiveIndex]; - } - return t.user_attrs?.[objectiveName] ?? null; - }).filter((v): v is number => v !== null && isFinite(v)); - - if (objValues.length < 3) return []; // Need at least 3 points for correlation - - const results: { name: string; correlation: number; absCorrelation: number }[] = []; - - designVariables.forEach(dv => { - const paramValues = trials - .map((t) => { - const objVal = t.values?.[objectiveIndex] ?? t.user_attrs?.[objectiveName]; - if (objVal === null || objVal === undefined || !isFinite(objVal)) return null; - return { param: t.params[dv.name], obj: objVal }; - }) - .filter((v): v is { param: number; obj: number } => v !== null && v.param !== undefined); - - if (paramValues.length < 3) return; - - const x = paramValues.map(v => v.param); - const y = paramValues.map(v => v.obj); - const corr = pearsonCorrelation(x, y); - - results.push({ - name: dv.name, - correlation: corr, - absCorrelation: Math.abs(corr) - }); - }); - - // Sort by absolute correlation or name - if (sortBy === 'importance') { - results.sort((a, b) => b.absCorrelation - a.absCorrelation); - } else { - results.sort((a, b) => a.name.localeCompare(b.name)); - } - - return results; - }, [trials, designVariables, objectiveIndex, objectiveName, sortBy]); - - if (!correlations.length) { - return ( -
- Not enough data to calculate parameter importance -
- ); - } - - // Build bar chart data - const names = correlations.map(c => c.name); - const values = correlations.map(c => c.correlation); - const colors = values.map(v => v > 0 ? '#EF4444' : '#22C55E'); // Red for positive (worse), Green for negative (better) when minimizing - const hoverTexts = correlations.map(c => - `${c.name}
Correlation: ${c.correlation.toFixed(4)}
|r|: ${c.absCorrelation.toFixed(4)}
${c.correlation > 0 ? 'Higher → Higher objective' : 'Higher → Lower objective'}` - ); - - return ( -
- {/* Controls */} -
-
- Correlation with {objectiveName} -
-
- - -
-
- - - - {/* Legend */} -
-
-
- Positive correlation (higher param → higher objective) -
-
-
- Negative correlation (higher param → lower objective) -
-
-
- ); -} diff --git a/atomizer-dashboard/frontend/src/components/plotly/PlotlyParetoPlot.tsx b/atomizer-dashboard/frontend/src/components/plotly/PlotlyParetoPlot.tsx deleted file mode 100644 index 1e7b1175..00000000 --- a/atomizer-dashboard/frontend/src/components/plotly/PlotlyParetoPlot.tsx +++ /dev/null @@ -1,448 +0,0 @@ -/** - * PlotlyParetoPlot - Interactive Pareto front visualization using Plotly - * - * Features: - * - 2D scatter with Pareto front highlighted - * - 3D scatter for 3-objective problems - * - Hover tooltips with trial details - * - Pareto front connection line - * - FEA vs NN differentiation - * - Constraint satisfaction highlighting - * - Dark mode styling - * - Zoom, pan, and export - */ - -import { useMemo, useState } from 'react'; -import Plot from 'react-plotly.js'; - -interface Trial { - trial_number: number; - values: number[]; - params: Record; - user_attrs?: Record; - source?: 'FEA' | 'NN' | 'V10_FEA'; - constraint_satisfied?: boolean; -} - -interface Objective { - name: string; - direction?: 'minimize' | 'maximize'; - unit?: string; -} - -interface PlotlyParetoPlotProps { - trials: Trial[]; - paretoFront: Trial[]; - objectives: Objective[]; - height?: number; - showParetoLine?: boolean; - showInfeasible?: boolean; -} - -export function PlotlyParetoPlot({ - trials, - paretoFront, - objectives, - height = 500, - showParetoLine = true, - showInfeasible = true -}: PlotlyParetoPlotProps) { - const [viewMode, setViewMode] = useState<'2d' | '3d'>(objectives.length >= 3 ? '3d' : '2d'); - const [selectedObjectives, setSelectedObjectives] = useState<[number, number, number]>([0, 1, 2]); - - const paretoSet = useMemo(() => new Set(paretoFront.map(t => t.trial_number)), [paretoFront]); - - // Separate trials by source, Pareto status, and constraint satisfaction - const { feaTrials, nnTrials, paretoTrials, infeasibleTrials, stats } = useMemo(() => { - const fea: Trial[] = []; - const nn: Trial[] = []; - const pareto: Trial[] = []; - const infeasible: Trial[] = []; - - trials.forEach(t => { - const source = t.source || t.user_attrs?.source || 'FEA'; - const isFeasible = t.constraint_satisfied !== false && t.user_attrs?.constraint_satisfied !== false; - - if (!isFeasible && showInfeasible) { - infeasible.push(t); - } else if (paretoSet.has(t.trial_number)) { - pareto.push(t); - } else if (source === 'NN') { - nn.push(t); - } else { - fea.push(t); - } - }); - - // Calculate statistics - const stats = { - totalTrials: trials.length, - paretoCount: pareto.length, - feaCount: fea.length + pareto.filter(t => (t.source || 'FEA') !== 'NN').length, - nnCount: nn.length + pareto.filter(t => t.source === 'NN').length, - infeasibleCount: infeasible.length, - hypervolume: 0 // Could calculate if needed - }; - - return { feaTrials: fea, nnTrials: nn, paretoTrials: pareto, infeasibleTrials: infeasible, stats }; - }, [trials, paretoSet, showInfeasible]); - - // Helper to get objective value - const getObjValue = (trial: Trial, idx: number): number => { - if (trial.values && trial.values[idx] !== undefined) { - return trial.values[idx]; - } - const objName = objectives[idx]?.name; - return trial.user_attrs?.[objName] ?? 0; - }; - - // Build hover text - const buildHoverText = (trial: Trial): string => { - const lines = [`Trial #${trial.trial_number}`]; - objectives.forEach((obj, i) => { - const val = getObjValue(trial, i); - lines.push(`${obj.name}: ${val.toFixed(4)}${obj.unit ? ` ${obj.unit}` : ''}`); - }); - const source = trial.source || trial.user_attrs?.source || 'FEA'; - lines.push(`Source: ${source}`); - return lines.join('
'); - }; - - // Create trace data - const createTrace = ( - trialList: Trial[], - name: string, - color: string, - symbol: string, - size: number, - opacity: number - ) => { - const [i, j, k] = selectedObjectives; - - if (viewMode === '3d' && objectives.length >= 3) { - return { - type: 'scatter3d' as const, - mode: 'markers' as const, - name, - x: trialList.map(t => getObjValue(t, i)), - y: trialList.map(t => getObjValue(t, j)), - z: trialList.map(t => getObjValue(t, k)), - text: trialList.map(buildHoverText), - hoverinfo: 'text' as const, - marker: { - color, - size, - symbol, - opacity, - line: { color: '#fff', width: 1 } - } - }; - } else { - return { - type: 'scatter' as const, - mode: 'markers' as const, - name, - x: trialList.map(t => getObjValue(t, i)), - y: trialList.map(t => getObjValue(t, j)), - text: trialList.map(buildHoverText), - hoverinfo: 'text' as const, - marker: { - color, - size, - symbol, - opacity, - line: { color: '#fff', width: 1 } - } - }; - } - }; - - // Sort Pareto trials by first objective for line connection - const sortedParetoTrials = useMemo(() => { - const [i] = selectedObjectives; - return [...paretoTrials].sort((a, b) => getObjValue(a, i) - getObjValue(b, i)); - }, [paretoTrials, selectedObjectives]); - - // Create Pareto front line trace (2D only) - const createParetoLine = () => { - if (!showParetoLine || viewMode === '3d' || sortedParetoTrials.length < 2) return null; - const [i, j] = selectedObjectives; - return { - type: 'scatter' as const, - mode: 'lines' as const, - name: 'Pareto Front', - x: sortedParetoTrials.map(t => getObjValue(t, i)), - y: sortedParetoTrials.map(t => getObjValue(t, j)), - line: { - color: '#10B981', - width: 2, - dash: 'dot' - }, - hoverinfo: 'skip' as const, - showlegend: false - }; - }; - - const traces = [ - // Infeasible trials (background, red X) - ...(showInfeasible && infeasibleTrials.length > 0 ? [ - createTrace(infeasibleTrials, `Infeasible (${infeasibleTrials.length})`, '#EF4444', 'x', 7, 0.4) - ] : []), - // FEA trials (blue circles) - createTrace(feaTrials, `FEA (${feaTrials.length})`, '#3B82F6', 'circle', 8, 0.6), - // NN trials (purple diamonds) - createTrace(nnTrials, `NN (${nnTrials.length})`, '#A855F7', 'diamond', 8, 0.5), - // Pareto front line (2D only) - createParetoLine(), - // Pareto front points (highlighted) - createTrace(sortedParetoTrials, `Pareto (${sortedParetoTrials.length})`, '#10B981', 'star', 14, 1.0) - ].filter(trace => trace && (trace.x as number[]).length > 0); - - const [i, j, k] = selectedObjectives; - - // Dark mode color scheme - const colors = { - text: '#E5E7EB', - textMuted: '#9CA3AF', - grid: 'rgba(255,255,255,0.1)', - zeroline: 'rgba(255,255,255,0.2)', - legendBg: 'rgba(30,30,30,0.9)', - legendBorder: 'rgba(255,255,255,0.1)' - }; - - const layout: any = viewMode === '3d' && objectives.length >= 3 - ? { - height, - margin: { l: 50, r: 50, t: 30, b: 50 }, - paper_bgcolor: 'transparent', - plot_bgcolor: 'transparent', - scene: { - xaxis: { - title: { text: objectives[i]?.name || 'Objective 1', font: { color: colors.text } }, - gridcolor: colors.grid, - zerolinecolor: colors.zeroline, - tickfont: { color: colors.textMuted } - }, - yaxis: { - title: { text: objectives[j]?.name || 'Objective 2', font: { color: colors.text } }, - gridcolor: colors.grid, - zerolinecolor: colors.zeroline, - tickfont: { color: colors.textMuted } - }, - zaxis: { - title: { text: objectives[k]?.name || 'Objective 3', font: { color: colors.text } }, - gridcolor: colors.grid, - zerolinecolor: colors.zeroline, - tickfont: { color: colors.textMuted } - }, - bgcolor: 'transparent' - }, - legend: { - x: 1, - y: 1, - font: { color: colors.text }, - bgcolor: colors.legendBg, - bordercolor: colors.legendBorder, - borderwidth: 1 - }, - font: { family: 'Inter, system-ui, sans-serif', color: colors.text } - } - : { - height, - margin: { l: 60, r: 30, t: 30, b: 60 }, - paper_bgcolor: 'transparent', - plot_bgcolor: 'transparent', - xaxis: { - title: { text: objectives[i]?.name || 'Objective 1', font: { color: colors.text } }, - gridcolor: colors.grid, - zerolinecolor: colors.zeroline, - tickfont: { color: colors.textMuted } - }, - yaxis: { - title: { text: objectives[j]?.name || 'Objective 2', font: { color: colors.text } }, - gridcolor: colors.grid, - zerolinecolor: colors.zeroline, - tickfont: { color: colors.textMuted } - }, - legend: { - x: 1, - y: 1, - xanchor: 'right', - font: { color: colors.text }, - bgcolor: colors.legendBg, - bordercolor: colors.legendBorder, - borderwidth: 1 - }, - font: { family: 'Inter, system-ui, sans-serif', color: colors.text }, - hovermode: 'closest' as const - }; - - if (!trials.length) { - return ( -
- No trial data available -
- ); - } - - return ( -
- {/* Stats Bar */} -
-
-
- Pareto: - {stats.paretoCount} -
-
-
- FEA: - {stats.feaCount} -
-
-
- NN: - {stats.nnCount} -
- {stats.infeasibleCount > 0 && ( -
-
- Infeasible: - {stats.infeasibleCount} -
- )} -
- - {/* Controls */} -
-
- {objectives.length >= 3 && ( -
- - -
- )} -
- - {/* Objective selectors */} -
- - - - - - - {viewMode === '3d' && objectives.length >= 3 && ( - <> - - - - )} -
-
- - - - {/* Pareto Front Table for 2D view */} - {viewMode === '2d' && sortedParetoTrials.length > 0 && ( -
- - - - - - - - - - - {sortedParetoTrials.slice(0, 10).map(trial => ( - - - - - - - ))} - -
Trial{objectives[i]?.name || 'Obj 1'}{objectives[j]?.name || 'Obj 2'}Source
#{trial.trial_number} - {getObjValue(trial, i).toExponential(4)} - - {getObjValue(trial, j).toExponential(4)} - - - {trial.source || trial.user_attrs?.source || 'FEA'} - -
- {sortedParetoTrials.length > 10 && ( -
- Showing 10 of {sortedParetoTrials.length} Pareto-optimal solutions -
- )} -
- )} -
- ); -} diff --git a/atomizer-dashboard/frontend/src/components/plotly/PlotlyRunComparison.tsx b/atomizer-dashboard/frontend/src/components/plotly/PlotlyRunComparison.tsx deleted file mode 100644 index 9a6ec3df..00000000 --- a/atomizer-dashboard/frontend/src/components/plotly/PlotlyRunComparison.tsx +++ /dev/null @@ -1,247 +0,0 @@ -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 NameSourceTrialsBest ValueAvg ValueDuration
{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; diff --git a/atomizer-dashboard/frontend/src/components/plotly/PlotlySurrogateQuality.tsx b/atomizer-dashboard/frontend/src/components/plotly/PlotlySurrogateQuality.tsx deleted file mode 100644 index e6aab4dc..00000000 --- a/atomizer-dashboard/frontend/src/components/plotly/PlotlySurrogateQuality.tsx +++ /dev/null @@ -1,202 +0,0 @@ -import { useMemo } from 'react'; -import Plot from 'react-plotly.js'; - -interface TrialData { - trial_number: number; - values: number[]; - source?: 'FEA' | 'NN' | 'V10_FEA'; - user_attrs?: Record; -} - -interface PlotlySurrogateQualityProps { - trials: TrialData[]; - height?: number; -} - -export function PlotlySurrogateQuality({ - trials, - height = 400 -}: PlotlySurrogateQualityProps) { - const { feaTrials, nnTrials, timeline } = useMemo(() => { - const fea = trials.filter(t => t.source === 'FEA' || t.source === 'V10_FEA'); - const nn = trials.filter(t => t.source === 'NN'); - - // Sort by trial number for timeline - const sorted = [...trials].sort((a, b) => a.trial_number - b.trial_number); - - // Calculate source distribution over time - const timeline: { trial: number; feaCount: number; nnCount: number }[] = []; - let feaCount = 0; - let nnCount = 0; - - sorted.forEach(t => { - if (t.source === 'NN') nnCount++; - else feaCount++; - - timeline.push({ - trial: t.trial_number, - feaCount, - nnCount - }); - }); - - return { - feaTrials: fea, - nnTrials: nn, - timeline - }; - }, [trials]); - - if (nnTrials.length === 0) { - return ( -
-

No neural network evaluations in this study

-
- ); - } - - // Objective distribution by source - const feaObjectives = feaTrials.map(t => t.values[0]).filter(v => v !== undefined && !isNaN(v)); - const nnObjectives = nnTrials.map(t => t.values[0]).filter(v => v !== undefined && !isNaN(v)); - - return ( -
- {/* Source Distribution Over Time */} - t.trial), - y: timeline.map(t => t.feaCount), - type: 'scatter', - mode: 'lines', - name: 'FEA Cumulative', - line: { color: '#3b82f6', width: 2 }, - fill: 'tozeroy', - fillcolor: 'rgba(59, 130, 246, 0.2)' - }, - { - x: timeline.map(t => t.trial), - y: timeline.map(t => t.nnCount), - type: 'scatter', - mode: 'lines', - name: 'NN Cumulative', - line: { color: '#a855f7', width: 2 }, - fill: 'tozeroy', - fillcolor: 'rgba(168, 85, 247, 0.2)' - } - ]} - layout={{ - title: { - text: 'Evaluation Source Over Time', - font: { color: '#fff', size: 14 } - }, - height: height * 0.6, - margin: { l: 60, r: 30, t: 50, b: 50 }, - paper_bgcolor: 'transparent', - plot_bgcolor: 'transparent', - xaxis: { - title: { text: 'Trial Number', font: { color: '#888' } }, - tickfont: { color: '#888' }, - gridcolor: 'rgba(255,255,255,0.05)' - }, - yaxis: { - title: { text: 'Cumulative Count', font: { color: '#888' } }, - tickfont: { color: '#888' }, - gridcolor: 'rgba(255,255,255,0.1)' - }, - legend: { - font: { color: '#888' }, - bgcolor: 'rgba(0,0,0,0.5)', - orientation: 'h', - y: 1.1 - }, - showlegend: true - }} - config={{ - displayModeBar: true, - modeBarButtonsToRemove: ['lasso2d', 'select2d'], - displaylogo: false - }} - style={{ width: '100%' }} - /> - - {/* Objective Distribution by Source */} - - - {/* FEA vs NN Best Values Comparison */} - {feaObjectives.length > 0 && nnObjectives.length > 0 && ( -
-
-
FEA Best
-
- {Math.min(...feaObjectives).toExponential(4)} -
-
- from {feaObjectives.length} evaluations -
-
-
-
NN Best
-
- {Math.min(...nnObjectives).toExponential(4)} -
-
- from {nnObjectives.length} predictions -
-
-
- )} -
- ); -} diff --git a/atomizer-dashboard/frontend/src/components/plotly/README.md b/atomizer-dashboard/frontend/src/components/plotly/README.md deleted file mode 100644 index ddf417e5..00000000 --- a/atomizer-dashboard/frontend/src/components/plotly/README.md +++ /dev/null @@ -1,217 +0,0 @@ -# Plotly Chart Components - -Interactive visualization components using Plotly.js for the Atomizer Dashboard. - -## Overview - -These components provide enhanced interactivity compared to Recharts: -- Native zoom, pan, and selection -- Export to PNG/SVG -- Hover tooltips with detailed information -- Brush filtering (parallel coordinates) -- 3D visualization support - -## Components - -### PlotlyParallelCoordinates - -Multi-dimensional data visualization showing relationships between all variables. - -```tsx -import { PlotlyParallelCoordinates } from '../components/plotly'; - - -``` - -**Props:** -| Prop | Type | Description | -|------|------|-------------| -| trials | Trial[] | All trial data | -| objectives | Objective[] | Objective definitions | -| designVariables | DesignVariable[] | Design variable definitions | -| paretoFront | Trial[] | Pareto-optimal trials (optional) | -| height | number | Chart height in pixels | - -**Features:** -- Drag on axes to filter data -- Double-click to reset filters -- Color coding: FEA (blue), NN (orange), Pareto (green) - -### PlotlyParetoPlot - -2D/3D scatter plot for Pareto front visualization. - -```tsx - -``` - -**Props:** -| Prop | Type | Description | -|------|------|-------------| -| trials | Trial[] | All trial data | -| paretoFront | Trial[] | Pareto-optimal trials | -| objectives | Objective[] | Objective definitions | -| height | number | Chart height in pixels | - -**Features:** -- Toggle between 2D and 3D views -- Axis selector for multi-objective problems -- Click to select trials -- Hover for trial details - -### PlotlyConvergencePlot - -Optimization progress over trials. - -```tsx - -``` - -**Props:** -| Prop | Type | Description | -|------|------|-------------| -| trials | Trial[] | All trial data | -| objectiveIndex | number | Which objective to plot | -| objectiveName | string | Objective display name | -| direction | 'minimize' \| 'maximize' | Optimization direction | -| height | number | Chart height | -| showRangeSlider | boolean | Show zoom slider | - -**Features:** -- Scatter points for each trial -- Best-so-far step line -- Range slider for zooming -- FEA vs NN differentiation - -### PlotlyParameterImportance - -Correlation-based parameter sensitivity analysis. - -```tsx - -``` - -**Props:** -| Prop | Type | Description | -|------|------|-------------| -| trials | Trial[] | All trial data | -| designVariables | DesignVariable[] | Design variables | -| objectiveIndex | number | Which objective | -| objectiveName | string | Objective display name | -| height | number | Chart height | - -**Features:** -- Horizontal bar chart of correlations -- Sort by importance or name -- Color: Red (positive), Green (negative) -- Pearson correlation coefficient - -## Bundle Optimization - -To minimize bundle size, we use: - -1. **plotly.js-basic-dist**: Smaller bundle (~1MB vs 3.5MB) - - Includes: scatter, bar, parcoords - - Excludes: 3D plots, maps, animations - -2. **Lazy Loading**: Components loaded on demand - ```tsx - const PlotlyParetoPlot = lazy(() => - import('./plotly/PlotlyParetoPlot') - .then(m => ({ default: m.PlotlyParetoPlot })) - ); - ``` - -3. **Code Splitting**: Vite config separates Plotly into its own chunk - ```ts - manualChunks: { - plotly: ['plotly.js-basic-dist', 'react-plotly.js'] - } - ``` - -## Usage with Suspense - -Always wrap Plotly components with Suspense: - -```tsx -}> - - -``` - -## Type Definitions - -```typescript -interface Trial { - trial_number: number; - values: number[]; - params: Record; - user_attrs?: Record; - source?: 'FEA' | 'NN' | 'V10_FEA'; -} - -interface Objective { - name: string; - direction?: 'minimize' | 'maximize'; - unit?: string; -} - -interface DesignVariable { - name: string; - unit?: string; - min?: number; - max?: number; -} -``` - -## Styling - -Components use transparent backgrounds for dark theme compatibility: -- `paper_bgcolor: 'rgba(0,0,0,0)'` -- `plot_bgcolor: 'rgba(0,0,0,0)'` -- Font: Inter, system-ui, sans-serif -- Grid colors: Tailwind gray palette - -## Export Options - -All Plotly charts include a mode bar with: -- Download PNG -- Download SVG (via menu) -- Zoom, Pan, Reset -- Auto-scale - -Configure export in the `config` prop: -```tsx -config={{ - toImageButtonOptions: { - format: 'png', - filename: 'my_chart', - height: 600, - width: 1200, - scale: 2 - } -}} -``` diff --git a/atomizer-dashboard/frontend/src/components/plotly/index.ts b/atomizer-dashboard/frontend/src/components/plotly/index.ts deleted file mode 100644 index a3ae83a7..00000000 --- a/atomizer-dashboard/frontend/src/components/plotly/index.ts +++ /dev/null @@ -1,15 +0,0 @@ -/** - * Plotly-based interactive chart components - * - * These components provide enhanced interactivity compared to Recharts: - * - Native zoom/pan - * - Brush selection on axes - * - 3D views for multi-objective problems - * - Export to PNG/SVG - * - Detailed hover tooltips - */ - -export { PlotlyParallelCoordinates } from './PlotlyParallelCoordinates'; -export { PlotlyParetoPlot } from './PlotlyParetoPlot'; -export { PlotlyConvergencePlot } from './PlotlyConvergencePlot'; -export { PlotlyParameterImportance } from './PlotlyParameterImportance';