Skip to content

Commit fb36d99

Browse files
authored
Merge pull request #7 from eWloYW8/master
feat: refactor trend chart to truncate timestamps to the nearest second
2 parents 039d6e9 + 02bb0c8 commit fb36d99

File tree

1 file changed

+59
-80
lines changed

1 file changed

+59
-80
lines changed

components/charts/echarts-trend-chart.tsx

Lines changed: 59 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"use client";
22

3+
import React, { useMemo } from 'react';
34
import ReactECharts from 'echarts-for-react';
45
import type { EChartsOption, LineSeriesOption } from 'echarts';
56
import { useTheme } from 'next-themes';
@@ -10,57 +11,70 @@ interface EchartsTrendChartProps {
1011
trendData: TrendEntry[];
1112
}
1213

14+
const truncateToSecond = (timestamp: number): number => {
15+
const date = new Date(timestamp);
16+
date.setMilliseconds(0);
17+
return date.getTime();
18+
};
19+
20+
1321
const EchartsTrendChart: React.FC<EchartsTrendChartProps> = ({ trendData }) => {
1422
const { theme } = useTheme();
1523

16-
const truncateToMinute = (timestamp: number): number => {
17-
const date = new Date(timestamp);
18-
date.setSeconds(0, 0);
19-
return date.getTime();
20-
};
21-
22-
const allTimePoints = new Set<number>();
23-
trendData.forEach(user => {
24-
user.history.forEach(point => {
25-
allTimePoints.add(truncateToMinute(new Date(point.time).getTime()));
24+
const sortedTimePoints = useMemo(() => {
25+
const allTimePoints = new Set<number>();
26+
trendData.forEach(user => {
27+
user.history.forEach(point => {
28+
allTimePoints.add(truncateToSecond(new Date(point.time).getTime()));
29+
});
2630
});
27-
});
31+
allTimePoints.add(truncateToSecond(new Date().getTime()));
2832

29-
allTimePoints.add(truncateToMinute(new Date().getTime()));
33+
return Array.from(allTimePoints).sort((a, b) => a - b);
34+
}, [trendData]);
3035

31-
const sortedTimePoints = Array.from(allTimePoints).sort((a, b) => a - b);
36+
const seriesData: LineSeriesOption[] = useMemo(() => {
37+
const sortedUserHistories = trendData.map(user => ({
38+
...user,
39+
history: user.history
40+
.map(p => ({ ...p, time: truncateToSecond(new Date(p.time).getTime()) }))
41+
.sort((a, b) => a.time - b.time),
42+
}));
3243

44+
const historyPointers: Record<string, number> = Object.fromEntries(trendData.map(u => [u.user_id, 0]));
45+
const currentUserScores: Record<string, number> = Object.fromEntries(trendData.map(u => [u.user_id, 0]));
46+
const seriesDataMap: Record<string, [number, number][]> = Object.fromEntries(trendData.map(u => [u.user_id, []]));
3347

34-
const seriesData: LineSeriesOption[] = trendData.map(user => {
35-
const data = sortedTimePoints.map(time => {
36-
const lastPoint = [...user.history]
37-
.filter(p => new Date(p.time).getTime() <= time)
38-
.pop();
39-
const score = lastPoint ? lastPoint.score : 0;
40-
return [time, score];
41-
});
42-
return {
48+
for (const masterTime of sortedTimePoints) {
49+
for (const user of sortedUserHistories) {
50+
while (
51+
historyPointers[user.user_id] < user.history.length &&
52+
user.history[historyPointers[user.user_id]].time <= masterTime
53+
) {
54+
currentUserScores[user.user_id] = user.history[historyPointers[user.user_id]].score;
55+
historyPointers[user.user_id]++;
56+
}
57+
seriesDataMap[user.user_id].push([masterTime, currentUserScores[user.user_id]]);
58+
}
59+
}
60+
61+
return trendData.map((user): LineSeriesOption => ({
4362
name: user.nickname,
4463
type: 'line',
4564
step: 'end',
4665
symbol: 'none',
47-
data: data,
48-
};
49-
});
66+
data: seriesDataMap[user.user_id],
67+
}));
68+
}, [trendData, sortedTimePoints]);
5069

5170
const option: EChartsOption = {
5271
backgroundColor: 'transparent',
5372
tooltip: {
5473
trigger: 'axis',
55-
axisPointer: {
56-
type: 'cross',
57-
label: {
58-
backgroundColor: '#6a7985'
59-
}
60-
},
6174
formatter: (params: any) => {
6275
const time = format(new Date(params[0].axisValue), 'yyyy-MM-dd HH:mm:ss');
6376
let tooltipHtml = `${time}<br/>`;
77+
params.sort((a: any, b: any) => b.value[1] - a.value[1]);
6478
params.forEach((param: any) => {
6579
tooltipHtml += `${param.marker} ${param.seriesName}: <strong>${param.value[1]}</strong><br/>`;
6680
});
@@ -69,18 +83,11 @@ const EchartsTrendChart: React.FC<EchartsTrendChartProps> = ({ trendData }) => {
6983
},
7084
legend: {
7185
data: trendData.map(user => user.nickname),
72-
textStyle: {
73-
color: theme === 'dark' ? '#ccc' : '#333',
74-
},
86+
textStyle: { color: theme === 'dark' ? '#ccc' : '#333' },
7587
bottom: 45,
7688
type: 'scroll',
7789
},
78-
grid: {
79-
left: '3%',
80-
right: '50px',
81-
bottom: 80,
82-
containLabel: true
83-
},
90+
grid: { left: '3%', right: '50px', bottom: 80, containLabel: true },
8491
toolbox: {
8592
feature: {
8693
saveAsImage: {
@@ -90,48 +97,20 @@ const EchartsTrendChart: React.FC<EchartsTrendChartProps> = ({ trendData }) => {
9097
}
9198
}
9299
},
93-
xAxis: [
94-
{
95-
type: 'time',
96-
axisLabel: {
97-
color: theme === 'dark' ? '#ccc' : '#333',
98-
formatter: (value: number) => {
99-
return format(new Date(value), 'yyyy-MM-dd HH:mm');
100-
}
101-
}
102-
}
103-
],
104-
yAxis: [
105-
{
106-
type: 'value',
107-
axisLabel: {
108-
color: theme === 'dark' ? '#ccc' : '#333'
109-
}
100+
xAxis: [{
101+
type: 'time',
102+
axisLabel: {
103+
color: theme === 'dark' ? '#ccc' : '#333',
104+
formatter: (value: number) => format(new Date(value), 'HH:mm:ss')
110105
}
111-
],
106+
}],
107+
yAxis: [{
108+
type: 'value',
109+
axisLabel: { color: theme === 'dark' ? '#ccc' : '#333' }
110+
}],
112111
dataZoom: [
113-
{
114-
type: 'slider',
115-
xAxisIndex: 0,
116-
start: 0,
117-
end: 100,
118-
bottom: 10,
119-
height: 20,
120-
},
121-
{
122-
type: 'slider',
123-
yAxisIndex: 0,
124-
start: 0,
125-
end: 100,
126-
right: 10,
127-
width: 20,
128-
},
129-
{
130-
type: 'inside',
131-
xAxisIndex: 0,
132-
start: 0,
133-
end: 100,
134-
},
112+
{ type: 'slider', xAxisIndex: 0, start: 0, end: 100, bottom: 10, height: 20 },
113+
{ type: 'inside', xAxisIndex: 0, start: 0, end: 100 },
135114
],
136115
series: seriesData,
137116
};

0 commit comments

Comments
 (0)