update
This commit is contained in:
@@ -4,7 +4,6 @@ import {
|
|||||||
Chart as ChartJS,
|
Chart as ChartJS,
|
||||||
CategoryScale,
|
CategoryScale,
|
||||||
Filler,
|
Filler,
|
||||||
Legend,
|
|
||||||
LinearScale,
|
LinearScale,
|
||||||
LineElement,
|
LineElement,
|
||||||
PointElement,
|
PointElement,
|
||||||
@@ -16,7 +15,6 @@ import { getTopACTrend } from "admin/api"
|
|||||||
ChartJS.register(
|
ChartJS.register(
|
||||||
CategoryScale,
|
CategoryScale,
|
||||||
Filler,
|
Filler,
|
||||||
Legend,
|
|
||||||
LinearScale,
|
LinearScale,
|
||||||
LineElement,
|
LineElement,
|
||||||
PointElement,
|
PointElement,
|
||||||
@@ -37,80 +35,78 @@ interface ProblemTrend {
|
|||||||
yearly: YearlyEntry[]
|
yearly: YearlyEntry[]
|
||||||
}
|
}
|
||||||
|
|
||||||
const COLORS = [
|
|
||||||
"#4e79a7",
|
|
||||||
"#f28e2b",
|
|
||||||
"#e15759",
|
|
||||||
"#76b7b2",
|
|
||||||
"#59a14f",
|
|
||||||
"#edc948",
|
|
||||||
"#b07aa1",
|
|
||||||
"#ff9da7",
|
|
||||||
"#9c755f",
|
|
||||||
"#bab0ac",
|
|
||||||
]
|
|
||||||
|
|
||||||
const loading = ref(true)
|
const loading = ref(true)
|
||||||
const data = ref<ProblemTrend[]>([])
|
const data = ref<ProblemTrend[]>([])
|
||||||
|
|
||||||
const allYears = computed(() => {
|
const acLabelPlugin = {
|
||||||
const years = new Set<number>()
|
id: "acLabel",
|
||||||
data.value.forEach((p) => p.yearly.forEach((y) => years.add(y.year)))
|
afterDatasetsDraw(chart: any) {
|
||||||
return Array.from(years).sort()
|
const ctx = chart.ctx
|
||||||
})
|
chart.data.datasets.forEach((_: any, i: number) => {
|
||||||
|
const meta = chart.getDatasetMeta(i)
|
||||||
|
meta.data.forEach((point: any, j: number) => {
|
||||||
|
const value = chart.data.datasets[i].data[j]
|
||||||
|
if (value === null || value === undefined) return
|
||||||
|
ctx.save()
|
||||||
|
ctx.font = "bold 11px sans-serif"
|
||||||
|
ctx.fillStyle = "rgba(99, 179, 237, 1)"
|
||||||
|
ctx.textAlign = "center"
|
||||||
|
ctx.textBaseline = "bottom"
|
||||||
|
ctx.fillText(`${value}%`, point.x, point.y - 6)
|
||||||
|
ctx.restore()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
const chartData = computed(() => ({
|
function getChartData(problem: ProblemTrend) {
|
||||||
labels: allYears.value.map(String),
|
return {
|
||||||
datasets: data.value.map((problem, i) => {
|
labels: problem.yearly.map((y) => String(y.year)),
|
||||||
const byYear = new Map(problem.yearly.map((y) => [y.year, y]))
|
datasets: [
|
||||||
return {
|
{
|
||||||
label: `${problem.problem_id} ${problem.problem_title}`,
|
label: "AC 率",
|
||||||
data: allYears.value.map((year) => byYear.get(year)?.ac_rate ?? null),
|
data: problem.yearly.map((y) => y.ac_rate),
|
||||||
borderColor: COLORS[i % COLORS.length],
|
fill: true,
|
||||||
backgroundColor: COLORS[i % COLORS.length] + "33",
|
tension: 0.3,
|
||||||
tension: 0.3,
|
backgroundColor: "rgba(99, 179, 237, 0.2)",
|
||||||
spanGaps: false,
|
borderColor: "rgba(99, 179, 237, 1)",
|
||||||
pointRadius: 4,
|
pointBackgroundColor: "rgba(99, 179, 237, 1)",
|
||||||
}
|
pointRadius: 4,
|
||||||
}),
|
},
|
||||||
}))
|
],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const chartOptions = {
|
function getChartOptions(problem: ProblemTrend) {
|
||||||
responsive: true,
|
return {
|
||||||
maintainAspectRatio: false,
|
responsive: true,
|
||||||
plugins: {
|
maintainAspectRatio: false,
|
||||||
title: {
|
plugins: {
|
||||||
display: true,
|
title: {
|
||||||
text: "提交次数前 10 题目 · 历年 AC 率",
|
display: true,
|
||||||
font: { size: 18 },
|
text: `${problem.problem_id} · ${problem.problem_title}`,
|
||||||
},
|
font: { size: 14 },
|
||||||
legend: {
|
},
|
||||||
position: "bottom" as const,
|
tooltip: {
|
||||||
labels: { boxWidth: 12, padding: 12 },
|
callbacks: {
|
||||||
},
|
label: (ctx: any) => {
|
||||||
tooltip: {
|
const entry = problem.yearly[ctx.dataIndex]
|
||||||
callbacks: {
|
return `AC 率: ${entry.ac_rate}% (${entry.accepted}/${entry.total})`
|
||||||
label: (ctx: any) => {
|
},
|
||||||
const problem = data.value[ctx.datasetIndex]
|
|
||||||
const year = allYears.value[ctx.dataIndex]
|
|
||||||
const entry = problem.yearly.find((y) => y.year === year)
|
|
||||||
if (!entry) return `${ctx.dataset.label}: 无数据`
|
|
||||||
return `${ctx.dataset.label}: ${entry.ac_rate}% (${entry.accepted}/${entry.total})`
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
scales: {
|
||||||
scales: {
|
y: {
|
||||||
y: {
|
min: 0,
|
||||||
min: 0,
|
max: 100,
|
||||||
max: 100,
|
ticks: { callback: (v: any) => `${v}%` },
|
||||||
ticks: { callback: (v: any) => `${v}%` },
|
},
|
||||||
title: { display: true, text: "AC 率" },
|
x: {
|
||||||
|
title: { display: true, text: "年份" },
|
||||||
|
},
|
||||||
},
|
},
|
||||||
x: {
|
}
|
||||||
title: { display: true, text: "年份" },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
@@ -124,21 +120,34 @@ onMounted(async () => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<h2 style="margin-top: 0">提交次数前 10 题目 · 历年 AC 率趋势</h2>
|
<h2 style="margin-top: 0">年度趋势</h2>
|
||||||
<n-spin :show="loading">
|
<n-spin :show="loading">
|
||||||
<div v-if="!loading && data.length === 0" style="text-align: center; padding: 40px">
|
<div
|
||||||
|
v-if="!loading && data.length === 0"
|
||||||
|
style="text-align: center; padding: 40px"
|
||||||
|
>
|
||||||
暂无数据
|
暂无数据
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="chart-wrapper">
|
<div v-else class="grid">
|
||||||
<Line :data="chartData" :options="chartOptions" />
|
<div v-for="problem in data" :key="problem.problem_id" class="chart-card">
|
||||||
|
<Line :data="getChartData(problem)" :options="getChartOptions(problem)" :plugins="[acLabelPlugin]" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</n-spin>
|
</n-spin>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.chart-wrapper {
|
.grid {
|
||||||
width: 100%;
|
display: grid;
|
||||||
height: 500px;
|
grid-template-columns: repeat(2, 1fr);
|
||||||
padding: 16px 0;
|
gap: 24px;
|
||||||
|
padding: 8px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-card {
|
||||||
|
height: 260px;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 8px;
|
||||||
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user