0.0.0.4
This commit is contained in:
@@ -0,0 +1,352 @@
|
||||
"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>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user