import { useState } from 'react'; import { BarChart, Bar, XAxis, YAxis, Tooltip, Cell, ResponsiveContainer } from 'recharts'; export default function TuningPanel({ tuningResult, tuning, loading, bestSize, setBestSize, onRun, }) { const [showFormula, setShowFormula] = useState(false); const best = tuningResult?.reduce((prev, curr) => curr.silhouette_score > prev.silhouette_score ? curr : prev ); const CustomTooltip = ({ active, payload }) => { if (!active || !payload?.length) return null; const d = payload[0].payload; return (
min_cluster_size = {d.min_cluster_size}
Clusters: {d.n_clusters}
Noise points: {d.n_noise}
Silhouette: {d.silhouette_score}
{d.min_cluster_size === best?.min_cluster_size && (
★ Best
)}
); }; return (
CLUSTER SIZE TUNING
{showFormula && (
SILHOUETTE SCORE — s(i)
s(i) = ( b(i) − a(i) ) / max( a(i), b(i) )
a(i) mean distance from point i to all other points in its own cluster — measures cohesion. Lower = tighter cluster.
b(i) mean distance from point i to all points in the nearest other cluster — measures separation. Higher = more distinct clusters.
s → +1 point is well inside its cluster, far from others. Good.
s → 0 point is on the boundary between two clusters. Ambiguous.
s → -1 point is closer to another cluster than its own. Misclassified.
)} {tuning && (
Testing cluster sizes 2, 3, 5, 8, 10, 15, 20, 30...
)} {tuningResult && !tuning && (
BEST SIZE setBestSize(Number(e.target.value))} style={{ width: 64, textAlign: 'center' }} /> {best.n_clusters} clusters · {best.n_noise} noise · silhouette {best.silhouette_score}
} /> {tuningResult.map((entry, i) => ( ))}
SIZE CLUSTERS NOISE SILHOUETTE
{tuningResult.map(r => (
{r.min_cluster_size} {r.min_cluster_size === best.min_cluster_size && ' ★'} {r.n_clusters} {r.n_noise} {r.silhouette_score}
))}
)}
); }