361 lines
13 KiB
TypeScript

import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { LineChart, BarChart3, PieChart } from "lucide-react";
import {
LineChart as RechartsLineChart,
Line,
XAxis,
YAxis,
CartesianGrid,
Tooltip,
Legend,
ResponsiveContainer,
BarChart,
Bar,
PieChart as RechartsPieChart,
Pie,
Cell,
} from "recharts";
import React from "react";
// Colors for charts
const CHART_COLORS = [
"#8884d8", "#83a6ed", "#8dd1e1", "#82ca9d", "#a4de6c",
"#d0ed57", "#ffc658", "#ff8042", "#ff6361", "#bc5090"
];
type TradeChartsProps = {
visualizationData: {
recordsByYear: { year: number; count: number }[];
topImporters: { country: string; count: number }[];
topExporters: { country: string; count: number }[];
termsTraded: { term: string; count: number }[];
tradePurposes: { purpose: string; count: number; description: string }[];
tradeSources: { source: string; count: number; description: string }[];
termQuantitiesByYear: { year: number; [term: string]: number }[];
topTerms: string[];
};
PURPOSE_DESCRIPTIONS: Record<string, string>;
SOURCE_DESCRIPTIONS: Record<string, string>;
};
export function TradeCharts({ visualizationData, PURPOSE_DESCRIPTIONS, SOURCE_DESCRIPTIONS }: TradeChartsProps) {
// Process terms data to combine small segments
const processedTermsData = React.useMemo(() => {
const threshold = 0.02; // 2% threshold
let otherCount = 0;
// Sort by count in descending order
const sortedTerms = [...visualizationData.termsTraded].sort((a, b) => b.count - a.count);
// Calculate total for percentage
const total = sortedTerms.reduce((sum, item) => sum + item.count, 0);
// Filter and combine small segments
const significantTerms = sortedTerms.filter(item => {
const percentage = item.count / total;
if (percentage < threshold) {
otherCount += item.count;
return false;
}
return true;
});
// Add "Other" category if there are small segments
if (otherCount > 0) {
significantTerms.push({ term: 'Other', count: otherCount });
}
return significantTerms;
}, [visualizationData.termsTraded]);
// Custom tooltip for pie chart
const renderCustomizedLabel = ({ cx, cy, midAngle, outerRadius, percent, payload }: any) => {
const RADIAN = Math.PI / 180;
const radius = outerRadius * 1.15; // Increased radius for better spacing
const x = cx + radius * Math.cos(-midAngle * RADIAN);
const y = cy + radius * Math.sin(-midAngle * RADIAN);
return (
<text
x={x}
y={y}
fill="#000000"
textAnchor={x > cx ? 'start' : 'end'}
dominantBaseline="central"
fontSize={12}
fontWeight="500"
>
{`${payload.term} (${(percent * 100).toFixed(0)}%)`}
</text>
);
};
return (
<div className="space-y-8">
<h3 className="text-lg font-semibold">Trade Visualizations</h3>
{/* Records Over Time */}
<Card>
<CardHeader className="pb-2">
<div className="flex items-center">
<LineChart className="mr-2 h-5 w-5 text-muted-foreground" />
<CardTitle className="text-base">Records Over Time</CardTitle>
</div>
<CardDescription>Number of trade records by year</CardDescription>
</CardHeader>
<CardContent>
<div className="h-[300px] w-full">
<ResponsiveContainer width="100%" height="100%">
<RechartsLineChart
data={visualizationData.recordsByYear}
margin={{ top: 5, right: 30, left: 20, bottom: 25 }}
>
<CartesianGrid strokeDasharray="3 3" />
<XAxis
dataKey="year"
angle={-45}
textAnchor="end"
tick={{ fontSize: 12 }}
/>
<YAxis />
<Tooltip />
<Legend />
<Line
type="monotone"
dataKey="count"
name="Records"
stroke="#8884d8"
activeDot={{ r: 8 }}
/>
</RechartsLineChart>
</ResponsiveContainer>
</div>
</CardContent>
</Card>
{/* Top Importers and Exporters */}
<div className="grid gap-6 md:grid-cols-2">
{/* Top Importers */}
<Card>
<CardHeader className="pb-2">
<div className="flex items-center">
<BarChart3 className="mr-2 h-5 w-5 text-muted-foreground" />
<CardTitle className="text-base">Top Importers</CardTitle>
</div>
<CardDescription>Countries importing the most specimens</CardDescription>
</CardHeader>
<CardContent>
<div className="h-[300px] w-full">
<ResponsiveContainer width="100%" height="100%">
<BarChart
data={visualizationData.topImporters}
margin={{ top: 5, right: 30, left: 20, bottom: 60 }}
>
<CartesianGrid strokeDasharray="3 3" />
<XAxis
dataKey="country"
angle={-45}
textAnchor="end"
tick={{ fontSize: 12 }}
interval={0}
/>
<YAxis />
<Tooltip />
<Bar dataKey="count" name="Records" fill="#8884d8" />
</BarChart>
</ResponsiveContainer>
</div>
</CardContent>
</Card>
{/* Top Exporters */}
<Card>
<CardHeader className="pb-2">
<div className="flex items-center">
<BarChart3 className="mr-2 h-5 w-5 text-muted-foreground" />
<CardTitle className="text-base">Top Exporters</CardTitle>
</div>
<CardDescription>Countries exporting the most specimens</CardDescription>
</CardHeader>
<CardContent>
<div className="h-[300px] w-full">
<ResponsiveContainer width="100%" height="100%">
<BarChart
data={visualizationData.topExporters}
margin={{ top: 5, right: 30, left: 20, bottom: 60 }}
>
<CartesianGrid strokeDasharray="3 3" />
<XAxis
dataKey="country"
angle={-45}
textAnchor="end"
tick={{ fontSize: 12 }}
interval={0}
/>
<YAxis />
<Tooltip />
<Bar dataKey="count" name="Records" fill="#82ca9d" />
</BarChart>
</ResponsiveContainer>
</div>
</CardContent>
</Card>
</div>
{/* Terms Traded */}
<Card>
<CardHeader className="pb-2">
<div className="flex items-center">
<PieChart className="mr-2 h-5 w-5 text-muted-foreground" />
<CardTitle className="text-base">Terms Traded</CardTitle>
</div>
<CardDescription>Distribution of specimen types in trade</CardDescription>
</CardHeader>
<CardContent>
<div className="h-[400px] w-full">
<ResponsiveContainer width="100%" height="100%">
<RechartsPieChart>
<Pie
data={processedTermsData}
cx="50%"
cy="50%"
labelLine={true}
label={renderCustomizedLabel}
outerRadius={130}
fill="#8884d8"
dataKey="count"
nameKey="term"
>
{processedTermsData.map((_, index) => (
<Cell key={`cell-${index}`} fill={CHART_COLORS[index % CHART_COLORS.length]} />
))}
</Pie>
<Tooltip formatter={(value, _, props) => [`${value} records`, props.payload.term]} />
</RechartsPieChart>
</ResponsiveContainer>
</div>
</CardContent>
</Card>
{/* Trade Purposes and Sources */}
<div className="grid gap-6 md:grid-cols-2">
{/* Trade Purposes */}
<Card>
<CardHeader className="pb-2">
<div className="flex items-center">
<BarChart3 className="mr-2 h-5 w-5 text-muted-foreground" />
<CardTitle className="text-base">Trade Purposes</CardTitle>
</div>
<CardDescription>Reasons for trade</CardDescription>
</CardHeader>
<CardContent>
<div className="h-[300px] w-full">
<ResponsiveContainer width="100%" height="100%">
<BarChart
data={visualizationData.tradePurposes}
margin={{ top: 5, right: 30, left: 20, bottom: 60 }}
layout="vertical"
>
<CartesianGrid strokeDasharray="3 3" />
<XAxis type="number" />
<YAxis
dataKey="purpose"
type="category"
tick={{ fontSize: 12 }}
tickFormatter={(value) => `${value} - ${PURPOSE_DESCRIPTIONS[value] || 'Unknown'}`}
width={150}
/>
<Tooltip formatter={(value, _, props) => [
`${value} records`,
`${props.payload.purpose} - ${props.payload.description}`
]} />
<Bar dataKey="count" name="Records" fill="#8884d8" />
</BarChart>
</ResponsiveContainer>
</div>
</CardContent>
</Card>
{/* Trade Sources */}
<Card>
<CardHeader className="pb-2">
<div className="flex items-center">
<BarChart3 className="mr-2 h-5 w-5 text-muted-foreground" />
<CardTitle className="text-base">Trade Sources</CardTitle>
</div>
<CardDescription>Origin of traded specimens</CardDescription>
</CardHeader>
<CardContent>
<div className="h-[300px] w-full">
<ResponsiveContainer width="100%" height="100%">
<BarChart
data={visualizationData.tradeSources}
margin={{ top: 5, right: 30, left: 20, bottom: 60 }}
layout="vertical"
>
<CartesianGrid strokeDasharray="3 3" />
<XAxis type="number" />
<YAxis
dataKey="source"
type="category"
tick={{ fontSize: 12 }}
tickFormatter={(value) => `${value} - ${SOURCE_DESCRIPTIONS[value] || 'Unknown'}`}
width={150}
/>
<Tooltip formatter={(value, _, props) => [
`${value} records`,
`${props.payload.source} - ${props.payload.description}`
]} />
<Bar dataKey="count" name="Records" fill="#82ca9d" />
</BarChart>
</ResponsiveContainer>
</div>
</CardContent>
</Card>
</div>
{/* Quantity of Top Terms Over Time */}
<Card>
<CardHeader className="pb-2">
<div className="flex items-center">
<LineChart className="mr-2 h-5 w-5 text-muted-foreground" />
<CardTitle className="text-base">Quantity of Top Terms Over Time</CardTitle>
</div>
<CardDescription>Trends in quantities of the most traded terms</CardDescription>
</CardHeader>
<CardContent>
<div className="h-[300px] w-full">
<ResponsiveContainer width="100%" height="100%">
<RechartsLineChart
data={visualizationData.termQuantitiesByYear}
margin={{ top: 5, right: 30, left: 20, bottom: 25 }}
>
<CartesianGrid strokeDasharray="3 3" />
<XAxis
dataKey="year"
angle={-45}
textAnchor="end"
tick={{ fontSize: 12 }}
/>
<YAxis />
<Tooltip />
<Legend />
{visualizationData.topTerms.map((term, index) => (
<Line
key={term}
type="monotone"
dataKey={term}
name={term}
stroke={CHART_COLORS[index % CHART_COLORS.length]}
activeDot={{ r: 8 }}
/>
))}
</RechartsLineChart>
</ResponsiveContainer>
</div>
</CardContent>
</Card>
</div>
);
}