353 lines
9.0 KiB
TypeScript
353 lines
9.0 KiB
TypeScript
"use client"
|
|
|
|
import { useEffect, useRef } from "react"
|
|
import * as echarts from "echarts"
|
|
import type { FamilyMember } from "@/types/family"
|
|
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card"
|
|
|
|
interface StatisticsChartsProps {
|
|
members: FamilyMember[]
|
|
}
|
|
|
|
export function StatisticsCharts({ members }: StatisticsChartsProps) {
|
|
const genderChartRef = useRef<HTMLDivElement>(null)
|
|
const generationChartRef = useRef<HTMLDivElement>(null)
|
|
const ageDistributionRef = useRef<HTMLDivElement>(null)
|
|
const birthYearChartRef = useRef<HTMLDivElement>(null)
|
|
|
|
useEffect(() => {
|
|
if (!genderChartRef.current || members.length === 0) return
|
|
|
|
// 性别分布饼图
|
|
const genderChart = echarts.init(genderChartRef.current)
|
|
const genderData = {
|
|
male: members.filter(m => m.gender === 'MALE').length,
|
|
female: members.filter(m => m.gender === 'FEMALE').length,
|
|
other: members.filter(m => m.gender === 'OTHER').length,
|
|
}
|
|
|
|
genderChart.setOption({
|
|
title: {
|
|
text: '性别分布',
|
|
left: 'center',
|
|
top: 10,
|
|
textStyle: {
|
|
fontSize: 14,
|
|
fontWeight: 'normal'
|
|
}
|
|
},
|
|
tooltip: {
|
|
trigger: 'item',
|
|
formatter: '{b}: {c} 人 ({d}%)'
|
|
},
|
|
legend: {
|
|
bottom: 10,
|
|
left: 'center'
|
|
},
|
|
series: [
|
|
{
|
|
type: 'pie',
|
|
radius: ['40%', '70%'],
|
|
center: ['50%', '50%'],
|
|
avoidLabelOverlap: false,
|
|
itemStyle: {
|
|
borderRadius: 10,
|
|
borderColor: '#fff',
|
|
borderWidth: 2
|
|
},
|
|
label: {
|
|
show: false
|
|
},
|
|
emphasis: {
|
|
label: {
|
|
show: true,
|
|
fontSize: 16,
|
|
fontWeight: 'bold'
|
|
}
|
|
},
|
|
data: [
|
|
{ value: genderData.male, name: '男性', itemStyle: { color: '#3b82f6' } },
|
|
{ value: genderData.female, name: '女性', itemStyle: { color: '#ec4899' } },
|
|
...(genderData.other > 0 ? [{ value: genderData.other, name: '其他', itemStyle: { color: '#8b5cf6' } }] : [])
|
|
]
|
|
}
|
|
]
|
|
})
|
|
|
|
return () => {
|
|
genderChart.dispose()
|
|
}
|
|
}, [members])
|
|
|
|
useEffect(() => {
|
|
if (!generationChartRef.current || members.length === 0) return
|
|
|
|
// 世代分布柱状图
|
|
const generationChart = echarts.init(generationChartRef.current)
|
|
const generationData = new Map<number, number>()
|
|
|
|
members.forEach(m => {
|
|
const gen = m.generation || 0
|
|
generationData.set(gen, (generationData.get(gen) || 0) + 1)
|
|
})
|
|
|
|
const sortedGenerations = Array.from(generationData.entries()).sort((a, b) => a[0] - b[0])
|
|
|
|
generationChart.setOption({
|
|
title: {
|
|
text: '世代分布',
|
|
left: 'center',
|
|
top: 10,
|
|
textStyle: {
|
|
fontSize: 14,
|
|
fontWeight: 'normal'
|
|
}
|
|
},
|
|
tooltip: {
|
|
trigger: 'axis',
|
|
axisPointer: {
|
|
type: 'shadow'
|
|
},
|
|
formatter: '{b}世: {c} 人'
|
|
},
|
|
grid: {
|
|
left: '3%',
|
|
right: '4%',
|
|
bottom: '3%',
|
|
top: '15%',
|
|
containLabel: true
|
|
},
|
|
xAxis: {
|
|
type: 'category',
|
|
data: sortedGenerations.map(([gen]) => `第${gen}世`),
|
|
axisLabel: {
|
|
rotate: 45
|
|
}
|
|
},
|
|
yAxis: {
|
|
type: 'value',
|
|
name: '人数'
|
|
},
|
|
series: [
|
|
{
|
|
type: 'bar',
|
|
data: sortedGenerations.map(([, count]) => count),
|
|
itemStyle: {
|
|
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
|
{ offset: 0, color: '#83bff6' },
|
|
{ offset: 0.5, color: '#188df0' },
|
|
{ offset: 1, color: '#188df0' }
|
|
])
|
|
},
|
|
emphasis: {
|
|
itemStyle: {
|
|
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
|
{ offset: 0, color: '#2378f7' },
|
|
{ offset: 0.7, color: '#2378f7' },
|
|
{ offset: 1, color: '#83bff6' }
|
|
])
|
|
}
|
|
}
|
|
}
|
|
]
|
|
})
|
|
|
|
return () => {
|
|
generationChart.dispose()
|
|
}
|
|
}, [members])
|
|
|
|
useEffect(() => {
|
|
if (!ageDistributionRef.current || members.length === 0) return
|
|
|
|
// 年龄分布图
|
|
const ageChart = echarts.init(ageDistributionRef.current)
|
|
const currentYear = new Date().getFullYear()
|
|
|
|
const ageGroups = {
|
|
'0-18': 0,
|
|
'19-35': 0,
|
|
'36-50': 0,
|
|
'51-65': 0,
|
|
'66+': 0,
|
|
'已故': 0
|
|
}
|
|
|
|
members.forEach(m => {
|
|
if (m.deathDate) {
|
|
ageGroups['已故']++
|
|
} else if (m.birthDate) {
|
|
const age = currentYear - new Date(m.birthDate).getFullYear()
|
|
if (age <= 18) ageGroups['0-18']++
|
|
else if (age <= 35) ageGroups['19-35']++
|
|
else if (age <= 50) ageGroups['36-50']++
|
|
else if (age <= 65) ageGroups['51-65']++
|
|
else ageGroups['66+']++
|
|
}
|
|
})
|
|
|
|
ageChart.setOption({
|
|
title: {
|
|
text: '年龄分布',
|
|
left: 'center',
|
|
top: 10,
|
|
textStyle: {
|
|
fontSize: 14,
|
|
fontWeight: 'normal'
|
|
}
|
|
},
|
|
tooltip: {
|
|
trigger: 'item',
|
|
formatter: '{b}: {c} 人 ({d}%)'
|
|
},
|
|
legend: {
|
|
bottom: 10,
|
|
left: 'center'
|
|
},
|
|
series: [
|
|
{
|
|
type: 'pie',
|
|
radius: '60%',
|
|
center: ['50%', '50%'],
|
|
data: Object.entries(ageGroups)
|
|
.filter(([, count]) => count > 0)
|
|
.map(([name, value]) => ({ name, value })),
|
|
emphasis: {
|
|
itemStyle: {
|
|
shadowBlur: 10,
|
|
shadowOffsetX: 0,
|
|
shadowColor: 'rgba(0, 0, 0, 0.5)'
|
|
}
|
|
}
|
|
}
|
|
]
|
|
})
|
|
|
|
return () => {
|
|
ageChart.dispose()
|
|
}
|
|
}, [members])
|
|
|
|
useEffect(() => {
|
|
if (!birthYearChartRef.current || members.length === 0) return
|
|
|
|
// 出生年份趋势图
|
|
const birthYearChart = echarts.init(birthYearChartRef.current)
|
|
const birthYearData = new Map<number, number>()
|
|
|
|
members.forEach(m => {
|
|
if (m.birthDate) {
|
|
const year = new Date(m.birthDate).getFullYear()
|
|
if (year >= 1900) { // 只统计1900年以后的
|
|
const decade = Math.floor(year / 10) * 10
|
|
birthYearData.set(decade, (birthYearData.get(decade) || 0) + 1)
|
|
}
|
|
}
|
|
})
|
|
|
|
const sortedYears = Array.from(birthYearData.entries()).sort((a, b) => a[0] - b[0])
|
|
|
|
birthYearChart.setOption({
|
|
title: {
|
|
text: '出生年代分布',
|
|
left: 'center',
|
|
top: 10,
|
|
textStyle: {
|
|
fontSize: 14,
|
|
fontWeight: 'normal'
|
|
}
|
|
},
|
|
tooltip: {
|
|
trigger: 'axis',
|
|
formatter: '{b}年代: {c} 人'
|
|
},
|
|
grid: {
|
|
left: '3%',
|
|
right: '4%',
|
|
bottom: '3%',
|
|
top: '15%',
|
|
containLabel: true
|
|
},
|
|
xAxis: {
|
|
type: 'category',
|
|
data: sortedYears.map(([year]) => `${year}s`),
|
|
axisLabel: {
|
|
rotate: 45
|
|
}
|
|
},
|
|
yAxis: {
|
|
type: 'value',
|
|
name: '人数'
|
|
},
|
|
series: [
|
|
{
|
|
type: 'line',
|
|
data: sortedYears.map(([, count]) => count),
|
|
smooth: true,
|
|
areaStyle: {
|
|
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
|
{ offset: 0, color: 'rgba(99, 102, 241, 0.5)' },
|
|
{ offset: 1, color: 'rgba(99, 102, 241, 0.1)' }
|
|
])
|
|
},
|
|
lineStyle: {
|
|
color: '#6366f1',
|
|
width: 2
|
|
},
|
|
itemStyle: {
|
|
color: '#6366f1'
|
|
}
|
|
}
|
|
]
|
|
})
|
|
|
|
return () => {
|
|
birthYearChart.dispose()
|
|
}
|
|
}, [members])
|
|
|
|
return (
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>性别分布</CardTitle>
|
|
<CardDescription>家族成员性别比例</CardDescription>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div ref={genderChartRef} className="h-[300px]" />
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>世代分布</CardTitle>
|
|
<CardDescription>各世代人数统计</CardDescription>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div ref={generationChartRef} className="h-[300px]" />
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>年龄分布</CardTitle>
|
|
<CardDescription>按年龄段划分的人数</CardDescription>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div ref={ageDistributionRef} className="h-[300px]" />
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>出生年代分布</CardTitle>
|
|
<CardDescription>各年代出生人数趋势</CardDescription>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div ref={birthYearChartRef} className="h-[300px]" />
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
)
|
|
}
|