[+] Chartjs chart

This commit is contained in:
2025-11-19 18:17:56 +08:00
parent 6f18e59fbe
commit 31662305b6
3 changed files with 111 additions and 54 deletions
+105 -54
View File
@@ -1,28 +1,102 @@
<script lang="ts">
import type { PageProps } from './$types';
import { goto } from '$app/navigation';
import AppBar from '../../../components/appbar/AppBar.svelte';
import { artistAndAlbum } from '../../../shared/tools';
import Button from '../../../components/Button.svelte';
import type { PageProps } from "./$types";
import { goto } from "$app/navigation";
import AppBar from "../../../components/appbar/AppBar.svelte";
import { artistAndAlbum } from "../../../shared/tools";
import Button from "../../../components/Button.svelte";
let { data }: PageProps = $props();
let { result, lrc } = data;
import Chart from "chart.js/auto";
// Destructure result for easier access
let { totalTyped, startTime, endTime, totalRight, statsHistory, songId } = result;
// Calculate duration for display
let duration = endTime - startTime;
let { data }: PageProps = $props();
let { result, lrc } = data;
let fields = [
{ label: '速度', value: Math.round(totalTyped / (Math.max(1, duration) / 60000)) },
{ label: '准确率', value: totalTyped === 0 ? 100 : (Math.round((totalRight / totalTyped) * 10000) / 100) },
{ label: '实时率', value: data.result.realTimeFactor.toFixed(2) + 'x' },
{ label: '字数', value: totalTyped }
]
// Destructure result for easier access
let { totalTyped, startTime, endTime, totalRight, statsHistory, songId } = result;
// Calculate duration for display
let duration = endTime - startTime;
let fields = [
{
label: "速度",
value: Math.round(totalTyped / (Math.max(1, duration) / 60000)),
},
{
label: "准确率",
value:
totalTyped === 0
? 100
: Math.round((totalRight / totalTyped) * 10000) / 100,
},
{ label: "实时率", value: data.result.realTimeFactor.toFixed(2) + "x" },
{ label: "字数", value: totalTyped },
];
let chartCanvas: HTMLCanvasElement;
let chart: Chart;
$effect(() => {
if (chartCanvas && statsHistory.length > 0) {
chart = new Chart(chartCanvas, {
type: "line",
data: {
labels: statsHistory.map((_: any, i: number) => i),
datasets: [
{
label: "速度 (CPM)",
data: statsHistory.map((h: any) => h.cpm),
tension: 0.4,
pointRadius: 0,
fill: true,
borderColor: "#7b78c2",
backgroundColor: "rgba(123, 120, 194, 0.1)",
},
{
label: "准确率 (%)",
data: statsHistory.map((h: any) => h.acc),
tension: 0.4,
yAxisID: "y1",
pointRadius: 0,
borderColor: "#e5a657",
},
],
},
options: {
responsive: true,
maintainAspectRatio: false,
interaction: {
mode: "index",
intersect: false,
},
plugins: {
legend: { display: false },
tooltip: { enabled: true },
},
scales: {
x: { display: false },
y: {
position: "left",
max: 300,
ticks: { display: false }
},
y1: {
position: "right",
max: 100,
grid: { display: false },
ticks: { display: false }
},
},
},
});
}
return () => {
if (chart) chart.destroy();
};
});
</script>
<AppBar title={data.brief.name} sub={artistAndAlbum(data.brief)}/>
<AppBar title={data.brief.name} sub={artistAndAlbum(data.brief)} />
<div class="vbox gap-16px p-content">
<div class="hbox gap-12px items-end! h-48px">
@@ -31,48 +105,25 @@
<div class="grid grid-cols-2 gap-16px">
{#each fields as field}
<div class="vbox flex-1">
<div class="m3-font-title-medium mfg-on-surface-variant">{field.label}</div>
<div class="m3-font-headline-large font-medium mfg-on-surface">{field.value}</div>
</div>
<div class="vbox flex-1">
<div class="m3-font-title-medium mfg-on-surface-variant">
{field.label}
</div>
<div class="m3-font-headline-large font-medium mfg-on-surface">
{field.value}
</div>
</div>
{/each}
</div>
<!-- Chart -->
<div class="h-120px w-full bg-surface-container rounded-12px relative overflow-hidden">
<svg class="w-full h-full" viewBox="0 0 100 100" preserveAspectRatio="none">
<!-- Grid lines -->
{#each [0, 25, 50, 75, 100] as y}
<line x1="0" y1={y} x2="100" y2={y} stroke="rgba(0,0,0,0.05)" stroke-width="1" />
{/each}
<!-- Speed Line -->
{#if statsHistory.length > 1}
<polyline
points={statsHistory.map((h: any, i: number) => `${(i / (statsHistory.length - 1)) * 100},${100 - (h.cpm / 300) * 100}`).join(' ')}
fill="none"
stroke="#7b78c2"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
/>
<!-- Accuracy Line (dashed) -->
<polyline
points={statsHistory.map((h: any, i: number) => `${(i / (statsHistory.length - 1)) * 100},${100 - (h.acc / 100) * 100}`).join(' ')}
fill="none"
stroke="#e5a657"
stroke-width="2"
stroke-dasharray="4"
stroke-opacity="0.5"
/>
{/if}
</svg>
<div class="h-200px w-full bg-surface-container rounded-12px relative overflow-hidden">
<canvas bind:this={chartCanvas}></canvas>
</div>
<div class="flex-1"></div>
<div class="hbox justify-end pt-8px">
<Button onclick={() => goto(`/song/${songId}`)}>下一首</Button>
<Button onclick={() => goto(`/song/${songId}`)}>下一首</Button>
</div>
</div>