From 604177bf0b85de7450b4df094a2f7a359d95a8d0 Mon Sep 17 00:00:00 2001 From: NianJiuZst <3235467914@qq.com> Date: Wed, 1 Apr 2026 13:52:14 +0800 Subject: [PATCH 1/2] fix(scatter-graphs): extract hiddenProviders to controlled props Move hiddenProviders state out of ScatterGraphs into LeaderboardView via controlled props. This enables parent-level synchronization of legend toggle state with header totalRuns. --- components/scatter-graphs.tsx | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/components/scatter-graphs.tsx b/components/scatter-graphs.tsx index dcef40d..ba19212 100644 --- a/components/scatter-graphs.tsx +++ b/components/scatter-graphs.tsx @@ -23,6 +23,9 @@ type GraphTab = 'perf-vs-cost' | 'perf-vs-speed' interface ScatterGraphsProps { entries: LeaderboardEntry[] scoreMode: 'best' | 'average' + // Controlled from LeaderboardView so hidden state syncs with Header + hiddenProviders: Set + onHiddenProvidersChange: (next: Set | ((prev: Set) => Set)) => void } interface DataPoint { @@ -285,16 +288,15 @@ function ProviderLegend({ providers, hiddenProviders, onToggle }: { // --- Main component --- -export function ScatterGraphs({ entries, scoreMode }: ScatterGraphsProps) { +export function ScatterGraphs({ entries, scoreMode, hiddenProviders, onHiddenProvidersChange }: ScatterGraphsProps) { const [graphTab, setGraphTab] = useState('perf-vs-cost') - const [hiddenProviders, setHiddenProviders] = useState>(new Set()) const toggleProvider = (provider: string) => { if (provider === '__reset__') { - setHiddenProviders(new Set()) + onHiddenProvidersChange(new Set()) return } - setHiddenProviders(prev => { + onHiddenProvidersChange(prev => { const next = new Set(prev) if (next.has(provider)) { next.delete(provider) From a921f9b13d529792d6fea8e903db9bc459e87b9c Mon Sep 17 00:00:00 2001 From: NianJiuZst <3235467914@qq.com> Date: Wed, 1 Apr 2026 13:52:19 +0800 Subject: [PATCH 2/2] fix(leaderboard-view): sync header totalRuns with scatter graph legend state Split filtering into two layers: - businessFilteredEntries: provider filter + open weights (legend stays complete) - scatterVisibleEntries: business filters + hiddenProviders (chart + header sync) headerEntries selects scatterVisibleEntries only when in scatter graph view, so totalRuns stays in sync with chart dots without affecting other sub-views. Also prune hiddenProviders when business filters change to avoid stale entries. --- components/leaderboard-view.tsx | 73 ++++++++++++++++++++++++--------- 1 file changed, 53 insertions(+), 20 deletions(-) diff --git a/components/leaderboard-view.tsx b/components/leaderboard-view.tsx index 0b34da2..1451dff 100644 --- a/components/leaderboard-view.tsx +++ b/components/leaderboard-view.tsx @@ -1,6 +1,6 @@ 'use client' -import { useCallback, useMemo, useState } from 'react' +import { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { useSearchParams, useRouter, usePathname } from 'next/navigation' import type { LeaderboardEntry, BenchmarkVersion } from '@/lib/types' import { PROVIDER_COLORS } from '@/lib/types' @@ -52,6 +52,8 @@ export function LeaderboardView({ entries, lastUpdated, versions, currentVersion const [providerFilter, setProviderFilterState] = useState(initialProvider) const [openWeightsOnly, setOpenWeightsOnlyState] = useState(initialOpenWeights) const [graphSubTab, setGraphSubTabState] = useState(initialGraphTab) + // Hidden providers — managed here so Header counts stay in sync with graph legend toggles + const [hiddenProviders, setHiddenProviders] = useState>(new Set()) // Helper to update URL params without full page reload const updateUrl = useCallback((updates: Record) => { @@ -100,31 +102,57 @@ export function LeaderboardView({ entries, lastUpdated, versions, currentVersion updateUrl({ official: v ? null : 'false' }) }, [updateUrl]) - const filteredEntries = useMemo(() => { - let result = entries - if (providerFilter) { - result = result.filter( - (entry) => entry.provider.toLowerCase() === providerFilter.toLowerCase() - ) - } - if (openWeightsOnly) { - result = result.filter((entry) => entry.weights === 'Open') - } - return result + // Business-level filters only (provider filter, open weights). + // Used for legend provider list and all charts/tables. + const businessFilteredEntries = useMemo(() => { + return entries.filter(entry => { + if (providerFilter && entry.provider.toLowerCase() !== providerFilter.toLowerCase()) return false + if (openWeightsOnly && entry.weights !== 'Open') return false + return true + }) }, [entries, providerFilter, openWeightsOnly]) + // Scatter-visible entries: business filters + legend-hidden providers. + // Used only for scatter graph: chart dots and header totalRuns. + const scatterVisibleEntries = useMemo(() => { + return businessFilteredEntries.filter(entry => + !hiddenProviders.has(entry.provider.toLowerCase()) + ) + }, [businessFilteredEntries, hiddenProviders]) + + // When business filters change, prune hiddenProviders to remove providers + // that are no longer present (e.g. after changing provider filter or open-weights). + const prevBusinessFiltersRef = useRef({ providerFilter, openWeightsOnly }) + useEffect(() => { + const prev = prevBusinessFiltersRef.current + if (prev.providerFilter !== providerFilter || prev.openWeightsOnly !== openWeightsOnly) { + prevBusinessFiltersRef.current = { providerFilter, openWeightsOnly } + const currentProviders = new Set(businessFilteredEntries.map(e => e.provider.toLowerCase())) + setHiddenProviders(prev => { + const pruned = new Set([...prev].filter(k => currentProviders.has(k))) + return pruned.size === prev.size ? prev : pruned + }) + } + }, [providerFilter, openWeightsOnly, businessFilteredEntries]) + const providerColor = providerFilter ? PROVIDER_COLORS[providerFilter.toLowerCase()] || '#666' : undefined + // Which entries to show in the Header stats: + // - In scatter graph view: use scatterVisibleEntries (excludes legend-hidden providers) + // - Otherwise: use businessFilteredEntries (legend-hidden providers don't affect totals) + const headerEntries = + view === 'graphs' && graphSubTab === 'scatter' ? scatterVisibleEntries : businessFilteredEntries + const totalRuns = useMemo(() => { - return filteredEntries.reduce((sum, entry) => sum + (entry.submission_count ?? 0), 0) - }, [filteredEntries]) + return headerEntries.reduce((sum, entry) => sum + (entry.submission_count ?? 0), 0) + }, [headerEntries]) return (
{graphSubTab === 'scatter' && ( - + )} {graphSubTab === 'heatmap' && ( - + )} {graphSubTab === 'distribution' && ( - + )} {graphSubTab === 'radar' && ( - + )}
) : (