import { useState, useMemo } from "react"; import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from "@/src/components/ui/card"; import { Tabs, TabsList, TabsTrigger } from "@/src/components/ui/tabs"; import { Loader2 } from "lucide-react"; import { useScoreAnalytics } from "../ScoreAnalyticsProvider"; import { ScoreDistributionBooleanChart } from "../charts/ScoreDistributionBooleanChart"; import { SamplingDetailsHoverCard } from "../SamplingDetailsHoverCard"; type DistributionTab = "score1" | "score2" | "all" | "matched"; /** * DistributionBooleanCard - Distribution chart card for BOOLEAN scores * * Responsibilities: * - Manage tab state (score1/score2/all/matched) * - Select appropriate distribution data and categories based on tab * - Apply solid color mapping (similar to numeric, not per-category) * - Render ScoreDistributionBooleanChart directly * * Key Implementation Details: * - Boolean scores always have exactly 2 categories (True/False) * - Individual tabs use solid colors like numeric charts * - Comparison tabs use namespaced categories * - Color logic is simpler than categorical (only 2 shades needed) */ export function DistributionBooleanCard() { const { data, isLoading, params, colorMappings, getColorForScore } = useScoreAnalytics(); const [activeTab, setActiveTab] = useState("all"); // Select distribution data and categories based on active tab const { distribution1Data, distribution2Data, categories, description } = useMemo(() => { if (!data) { return { distribution1Data: [], distribution2Data: undefined, categories: [], description: "", }; } const { distribution, metadata, statistics } = data; const { mode } = metadata; const { score1, score2 } = params; if (mode === "single") { // Single score mode - only show individual distribution return { distribution1Data: distribution.score1, distribution2Data: undefined, categories: distribution.categories ?? [], description: `${statistics.score1.total.toLocaleString()} observations${ statistics.score1.mode ? ` | Most frequent: ${statistics.score1.mode.category} (${statistics.score1.mode.count.toLocaleString()})` : "" }`, }; } // Two score mode - handle tabs switch (activeTab) { case "score1": return { distribution1Data: distribution.score1Individual, distribution2Data: undefined, categories: distribution.categories ?? [], description: `${score1.name} - ${statistics.score1.total.toLocaleString()} observations`, }; case "score2": return { distribution1Data: distribution.score2Individual, distribution2Data: undefined, categories: distribution.score2Categories ?? [], description: `${score2?.name ?? "Score 2"} - ${statistics.score2?.total.toLocaleString()} observations`, }; case "all": return { distribution1Data: distribution.score1Individual, distribution2Data: distribution.score2Individual, categories: distribution.categories ?? [], description: `${score1.name} (${statistics.score1.total.toLocaleString()}) vs ${score2?.name} (${statistics.score2?.total.toLocaleString()})`, }; case "matched": return { distribution1Data: distribution.score1Matched, distribution2Data: distribution.score2Matched, categories: distribution.categories ?? [], description: `${score1.name} vs ${score2?.name} - ${statistics.comparison?.matchedCount.toLocaleString()} matched`, }; } }, [data, activeTab, params]); // Build color mapping for boolean charts const chartColors = useMemo(() => { if (!data) return colorMappings; // For individual tabs, use solid colors like numeric charts if (activeTab === "score1") { return { score1: getColorForScore(1) }; } if (activeTab === "score2") { // Visual slot 1, but score2's color return { score1: getColorForScore(2) }; } // For "all" or "matched" tabs with boolean data, use solid colors return { score1: getColorForScore(1), score2: getColorForScore(2), }; }, [activeTab, data, colorMappings, getColorForScore]); // Loading state if (isLoading) { return ( Distribution Loading chart... ); } // No data state if (!data) { return ( Distribution No data available Select a score to view distribution ); } const { metadata } = data; const { mode } = metadata; const { score1, score2 } = params; const hasData = distribution1Data.length > 0; const showTabs = mode === "two"; // Helper function to truncate tab labels with max character limit const truncateLabel = (label: string): string => { if (label.length <= 20) return label; return label.substring(0, 17) + "..."; }; // Build full tab labels for title attribute (hover tooltip) const score1FullLabel = score1.name === score2?.name ? `${score1.source} · ${score1.name}` : score1.name; const score2FullLabel = score2 ? score2.name === score1.name ? `${score2.source} · ${score2.name}` : score2.name : "Score 2"; return (
Distribution {data.samplingMetadata.isSampled && ( )} {description}
{showTabs && ( setActiveTab(v as DistributionTab)} > {truncateLabel(score1FullLabel)} {truncateLabel(score2FullLabel)} all matched )}
{hasData ? ( ) : (
No distribution data available for the selected time range
)}
); }