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}
))}
)}
);
}