diff --git a/src/components/time-series-chart.tsx b/src/components/time-series-chart.tsx index 20b052fd..462c6fc6 100644 --- a/src/components/time-series-chart.tsx +++ b/src/components/time-series-chart.tsx @@ -121,7 +121,7 @@ export function TimeSeriesChart({ // `excluded` flag drives both opacity (CSS fade-out) and Y-axis math // (excluded values are dropped from min/max so the remaining lines // can spread out vertically when an outlier is hidden). - const lines = useMemo(() => { + const allLines = useMemo(() => { const built = benchmark.results .map((r) => ({ slug: r.slug, @@ -138,6 +138,16 @@ export function TimeSeriesChart({ return built; }, [benchmark, range, region, colors, excluded, seriesOverride]); + // Top-N selector. When the cohort grows past ~20 series the chart + // becomes visually noisy and the legend wraps to multiple lines. + // null = show all; number = clip to the top N by recent mean. + const TOP_N_DEFAULT = allLines.length > 20 ? 20 : null; + const [topN, setTopN] = useState(TOP_N_DEFAULT); + const lines = useMemo(() => { + if (topN == null) return allLines; + return allLines.slice(0, topN); + }, [allLines, topN]); + // A key that flips when the data shape changes. used to retrigger // the line-draw animation. const seriesKey = `${range}::${region}::${metricLabelOverride ?? "default"}`; @@ -251,6 +261,33 @@ export function TimeSeriesChart({ ))} )} + + {allLines.length > 10 && ( +
+ + Show + + {([5, 10, 20, null] as const).map((n) => { + const active = topN === n; + const label = n == null ? "All" : `Top ${n}`; + return ( + + ); + })} +
+ )} {lines.length === 0 ? (