|
|
"use client" |
|
|
|
|
|
import { useMemo } from "react" |
|
|
import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, PieChart, Pie, Cell, Legend } from "recharts" |
|
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" |
|
|
|
|
|
interface Dataset { |
|
|
Name: string |
|
|
Category: string |
|
|
Description: string |
|
|
Task: string |
|
|
Data_Type: string |
|
|
Source: string |
|
|
"Paper link": string |
|
|
Availability: string |
|
|
Contact: string |
|
|
} |
|
|
|
|
|
interface DatasetStatisticsProps { |
|
|
datasets: Dataset[] |
|
|
selectedCategory: string | null |
|
|
selectedDataType: string | null |
|
|
selectedAvailability: string | null |
|
|
onCategoryClick: (category: string) => void |
|
|
onDataTypeClick: (dataType: string) => void |
|
|
onAvailabilityClick: (availability: string) => void |
|
|
} |
|
|
|
|
|
const CATEGORY_COLORS = [ |
|
|
"#6366f1", |
|
|
"#10b981", |
|
|
"#f59e0b", |
|
|
"#3b82f6", |
|
|
"#8b5cf6", |
|
|
"#06b6d4", |
|
|
] |
|
|
|
|
|
const DATA_TYPE_COLORS = [ |
|
|
"#3b82f6", |
|
|
"#10b981", |
|
|
"#6366f1", |
|
|
"#f59e0b", |
|
|
"#8b5cf6", |
|
|
"#06b6d4", |
|
|
] |
|
|
|
|
|
const AVAILABILITY_COLORS = { |
|
|
"Open source": "#3b82f6", |
|
|
"Gated": "#10b981", |
|
|
"Unknown": "#6b7280", |
|
|
} |
|
|
|
|
|
export function DatasetStatistics({ |
|
|
datasets, |
|
|
selectedCategory, |
|
|
selectedDataType, |
|
|
selectedAvailability, |
|
|
onCategoryClick, |
|
|
onDataTypeClick, |
|
|
onAvailabilityClick |
|
|
}: DatasetStatisticsProps) { |
|
|
const categoryData = useMemo(() => { |
|
|
const counts: Record<string, number> = {} |
|
|
datasets.forEach((dataset) => { |
|
|
if (dataset.Category) { |
|
|
counts[dataset.Category] = (counts[dataset.Category] || 0) + 1 |
|
|
} |
|
|
}) |
|
|
return Object.entries(counts) |
|
|
.map(([name, value]) => ({ name, value })) |
|
|
.sort((a, b) => b.value - a.value) |
|
|
}, [datasets]) |
|
|
|
|
|
const dataTypeData = useMemo(() => { |
|
|
const counts: Record<string, number> = {} |
|
|
datasets.forEach((dataset) => { |
|
|
if (dataset.Data_Type) { |
|
|
counts[dataset.Data_Type] = (counts[dataset.Data_Type] || 0) + 1 |
|
|
} |
|
|
}) |
|
|
return Object.entries(counts) |
|
|
.map(([name, value]) => ({ name, value })) |
|
|
.sort((a, b) => b.value - a.value) |
|
|
}, [datasets]) |
|
|
|
|
|
const availabilityData = useMemo(() => { |
|
|
const counts: Record<string, number> = {} |
|
|
datasets.forEach((dataset) => { |
|
|
if (dataset.Availability) { |
|
|
counts[dataset.Availability] = (counts[dataset.Availability] || 0) + 1 |
|
|
} |
|
|
}) |
|
|
return Object.entries(counts).map(([name, value]) => ({ name, value })) |
|
|
}, [datasets]) |
|
|
|
|
|
const CustomTooltip = ({ active, payload }: any) => { |
|
|
if (active && payload && payload.length) { |
|
|
return ( |
|
|
<div className="bg-popover border border-border rounded-lg shadow-lg p-3"> |
|
|
<p className="font-medium text-sm">{payload[0].payload.name}</p> |
|
|
<p className="text-sm text-muted-foreground">Count: {payload[0].value}</p> |
|
|
</div> |
|
|
) |
|
|
} |
|
|
return null |
|
|
} |
|
|
|
|
|
const renderCustomBarLabel = (props: any) => { |
|
|
const { x, y, width, height, value } = props |
|
|
return ( |
|
|
<text |
|
|
x={x + width + 5} |
|
|
y={y + height / 2} |
|
|
fill="currentColor" |
|
|
className="text-sm font-medium fill-foreground" |
|
|
textAnchor="start" |
|
|
dominantBaseline="middle" |
|
|
> |
|
|
{value} |
|
|
</text> |
|
|
) |
|
|
} |
|
|
|
|
|
return ( |
|
|
<div className="space-y-6 mb-8"> |
|
|
{/* Top Row: By Data Type and Availability */} |
|
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6"> |
|
|
{/* By Data Type */} |
|
|
<Card> |
|
|
<CardHeader> |
|
|
<CardTitle className="text-lg font-semibold"> |
|
|
By Data Type |
|
|
{selectedDataType && ( |
|
|
<span className="text-sm font-normal text-muted-foreground ml-2"> |
|
|
(Click to clear filter) |
|
|
</span> |
|
|
)} |
|
|
</CardTitle> |
|
|
</CardHeader> |
|
|
<CardContent> |
|
|
<ResponsiveContainer width="100%" height={300}> |
|
|
<BarChart |
|
|
data={dataTypeData} |
|
|
layout="vertical" |
|
|
margin={{ top: 5, right: 40, left: 5, bottom: 5 }} |
|
|
> |
|
|
<CartesianGrid strokeDasharray="3 3" className="stroke-border" /> |
|
|
<XAxis type="number" className="text-xs" tick={{ fill: "currentColor" }} /> |
|
|
<YAxis |
|
|
type="category" |
|
|
dataKey="name" |
|
|
width={100} |
|
|
className="text-xs" |
|
|
tick={{ fill: "currentColor" }} |
|
|
/> |
|
|
<Tooltip content={<CustomTooltip />} /> |
|
|
<Bar |
|
|
dataKey="value" |
|
|
radius={[0, 4, 4, 0]} |
|
|
label={renderCustomBarLabel} |
|
|
onClick={(data) => onDataTypeClick(data.name)} |
|
|
cursor="pointer" |
|
|
> |
|
|
{dataTypeData.map((entry, index) => { |
|
|
const isSelected = selectedDataType === entry.name |
|
|
const shouldGreyscale = selectedDataType && !isSelected |
|
|
const color = shouldGreyscale |
|
|
? "#94a3b8" |
|
|
: DATA_TYPE_COLORS[index % DATA_TYPE_COLORS.length] |
|
|
return ( |
|
|
<Cell |
|
|
key={`cell-${index}`} |
|
|
fill={color} |
|
|
opacity={shouldGreyscale ? 0.4 : 1} |
|
|
/> |
|
|
) |
|
|
})} |
|
|
</Bar> |
|
|
</BarChart> |
|
|
</ResponsiveContainer> |
|
|
</CardContent> |
|
|
</Card> |
|
|
|
|
|
{/* Availability */} |
|
|
<Card> |
|
|
<CardHeader> |
|
|
<CardTitle className="text-lg font-semibold"> |
|
|
Availability |
|
|
{selectedAvailability && ( |
|
|
<span className="text-sm font-normal text-muted-foreground ml-2"> |
|
|
(Click to clear filter) |
|
|
</span> |
|
|
)} |
|
|
</CardTitle> |
|
|
</CardHeader> |
|
|
<CardContent> |
|
|
<ResponsiveContainer width="100%" height={300}> |
|
|
<PieChart> |
|
|
<Pie |
|
|
data={availabilityData} |
|
|
cx="50%" |
|
|
cy="50%" |
|
|
labelLine={false} |
|
|
label={({ name, value, percent }) => `${name}: ${value}`} |
|
|
outerRadius={80} |
|
|
fill="#8884d8" |
|
|
dataKey="value" |
|
|
onClick={(data) => onAvailabilityClick(data.name)} |
|
|
cursor="pointer" |
|
|
> |
|
|
{availabilityData.map((entry, index) => { |
|
|
const isSelected = selectedAvailability === entry.name |
|
|
const shouldGreyscale = selectedAvailability && !isSelected |
|
|
const originalColor = AVAILABILITY_COLORS[entry.name as keyof typeof AVAILABILITY_COLORS] || "#6b7280" |
|
|
const color = shouldGreyscale ? "#94a3b8" : originalColor |
|
|
return ( |
|
|
<Cell |
|
|
key={`cell-${index}`} |
|
|
fill={color} |
|
|
opacity={shouldGreyscale ? 0.4 : 1} |
|
|
/> |
|
|
) |
|
|
})} |
|
|
</Pie> |
|
|
<Tooltip content={<CustomTooltip />} /> |
|
|
<Legend |
|
|
verticalAlign="bottom" |
|
|
height={36} |
|
|
iconType="circle" |
|
|
formatter={(value) => <span className="text-sm">{value}</span>} |
|
|
/> |
|
|
</PieChart> |
|
|
</ResponsiveContainer> |
|
|
</CardContent> |
|
|
</Card> |
|
|
</div> |
|
|
|
|
|
{/* Bottom Row: By Category */} |
|
|
<div className="grid grid-cols-1 gap-6"> |
|
|
<Card> |
|
|
<CardHeader> |
|
|
<CardTitle className="text-lg font-semibold"> |
|
|
By Category |
|
|
{selectedCategory && ( |
|
|
<span className="text-sm font-normal text-muted-foreground ml-2"> |
|
|
(Click to clear filter) |
|
|
</span> |
|
|
)} |
|
|
</CardTitle> |
|
|
</CardHeader> |
|
|
<CardContent> |
|
|
<ResponsiveContainer width="100%" height={300}> |
|
|
<BarChart |
|
|
data={categoryData} |
|
|
layout="vertical" |
|
|
margin={{ top: 5, right: 40, left: 5, bottom: 5 }} |
|
|
> |
|
|
<CartesianGrid strokeDasharray="3 3" className="stroke-border" /> |
|
|
<XAxis type="number" className="text-xs" tick={{ fill: "currentColor" }} /> |
|
|
<YAxis |
|
|
type="category" |
|
|
dataKey="name" |
|
|
width={180} |
|
|
className="text-xs" |
|
|
tick={{ fill: "currentColor" }} |
|
|
/> |
|
|
<Tooltip content={<CustomTooltip />} /> |
|
|
<Bar |
|
|
dataKey="value" |
|
|
radius={[0, 4, 4, 0]} |
|
|
label={renderCustomBarLabel} |
|
|
onClick={(data) => onCategoryClick(data.name)} |
|
|
cursor="pointer" |
|
|
> |
|
|
{categoryData.map((entry, index) => { |
|
|
const isSelected = selectedCategory === entry.name |
|
|
const shouldGreyscale = selectedCategory && !isSelected |
|
|
const color = shouldGreyscale |
|
|
? "#94a3b8" |
|
|
: CATEGORY_COLORS[index % CATEGORY_COLORS.length] |
|
|
return ( |
|
|
<Cell |
|
|
key={`cell-${index}`} |
|
|
fill={color} |
|
|
opacity={shouldGreyscale ? 0.4 : 1} |
|
|
/> |
|
|
) |
|
|
})} |
|
|
</Bar> |
|
|
</BarChart> |
|
|
</ResponsiveContainer> |
|
|
</CardContent> |
|
|
</Card> |
|
|
</div> |
|
|
</div> |
|
|
) |
|
|
} |
|
|
|