Skip to content

Commit 09ac19c

Browse files
add : add TEE Climbing chart to UserPage.
1 parent 054346a commit 09ac19c

File tree

3 files changed

+122
-1
lines changed

3 files changed

+122
-1
lines changed
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import React from "react";
2+
import { Row } from "reactstrap";
3+
import {
4+
XAxis,
5+
YAxis,
6+
CartesianGrid,
7+
Tooltip,
8+
LineChart,
9+
Line,
10+
ResponsiveContainer,
11+
} from "recharts";
12+
import { formatMomentDate, parseSecond } from "../../../utils/DateUtil";
13+
14+
interface Props {
15+
climbingData: { dateSecond: number; count: number }[];
16+
}
17+
18+
export const TeeChart: React.FC<Props> = (props) => (
19+
<Row className="my-3">
20+
<ResponsiveContainer width="100%" height={300}>
21+
<LineChart
22+
data={props.climbingData}
23+
margin={{
24+
top: 5,
25+
right: 30,
26+
left: 20,
27+
bottom: 5,
28+
}}
29+
>
30+
<CartesianGrid strokeDasharray="3 3" />
31+
<XAxis
32+
dataKey="dateSecond"
33+
type="number"
34+
domain={["dataMin", "dataMax"]}
35+
tickFormatter={(dateSecond: number): string =>
36+
formatMomentDate(parseSecond(dateSecond))
37+
}
38+
/>
39+
<YAxis />
40+
<Tooltip
41+
labelFormatter={(dateSecond: number | string): string =>
42+
typeof dateSecond === "number"
43+
? formatMomentDate(parseSecond(dateSecond))
44+
: dateSecond
45+
}
46+
/>
47+
<Line dataKey="count" stroke="#8884d8" />
48+
</LineChart>
49+
</ResponsiveContainer>
50+
</Row>
51+
);

atcoder-problems-frontend/src/pages/UserPage/ProgressChartBlock/index.tsx

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,18 @@ import {
2424
parseDateLabel,
2525
} from "../../../utils/DateUtil";
2626
import { useLocalStorage } from "../../../utils/LocalStorage";
27-
import { countUniqueAcByDate } from "../../../utils/StreakCounter";
27+
import {
28+
countUniqueAcByDate,
29+
countTeeByDate,
30+
} from "../../../utils/StreakCounter";
2831
import Submission from "../../../interfaces/Submission";
2932
import { ProblemId } from "../../../interfaces/Status";
3033
import { DailyEffortBarChart } from "./DailyEffortBarChart";
3134
import { DailyEffortStackedBarChart } from "./DailyEffortStackedBarChart";
3235
import { ClimbingLineChart } from "./ClimbingLineChart";
3336
import { ClimbingAreaChart } from "./ClimbingAreaChart";
3437
import { FilteringHeatmap } from "./FilteringHeatmap";
38+
import { TeeChart } from "./TeeChart";
3539

3640
const chartTypes = ["Simple", "Colored"] as const;
3741
type ChartType = typeof chartTypes[number];
@@ -130,6 +134,18 @@ export const ProgressChartBlock: React.FC<Props> = (props) => {
130134
return list;
131135
}, [] as { dateSecond: number; count: number }[]);
132136

137+
const dailyTeeCount = countTeeByDate(userSubmissions, problemModels);
138+
const teeClimbing = dailyTeeCount.reduce((list, { dateLabel, count }) => {
139+
const dateSecond = parseDateLabel(dateLabel).unix();
140+
const last = list.length === 0 ? undefined : list[list.length - 1];
141+
if (last) {
142+
list.push({ dateSecond, count: last.count + count });
143+
} else {
144+
list.push({ dateSecond, count });
145+
}
146+
return list;
147+
}, [] as { dateSecond: number; count: number }[]);
148+
133149
const dateColorCountMap = Array.from(submissionsByProblem.values())
134150
.map((submissionsOfProblem) => {
135151
const accepted = submissionsOfProblem
@@ -256,6 +272,11 @@ export const ProgressChartBlock: React.FC<Props> = (props) => {
256272
/>
257273
)}
258274

275+
<Row className="my-2 border-bottom">
276+
<h1>TEE Climbing</h1>
277+
</Row>
278+
<TeeChart climbingData={teeClimbing} />
279+
259280
<Row className="my-2 border-bottom">
260281
<h1>Heatmap</h1>
261282
</Row>

atcoder-problems-frontend/src/utils/StreakCounter.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
import { ProblemId } from "../interfaces/Status";
22
import Submission from "../interfaces/Submission";
3+
import ProblemModel, {
4+
isProblemModelWithTimeModel,
5+
} from "../interfaces/ProblemModel";
36
import { formatMomentDate, parseDateLabel, parseSecond } from "./DateUtil";
7+
import { calculateTopPlayerEquivalentEffort } from "./ProblemModelUtil";
48
import { isAccepted } from "./index";
59

610
export interface Streak {
@@ -62,3 +66,48 @@ export const countUniqueAcByDate = (
6266
.map(([dateLabel, count]) => ({ dateLabel, count }))
6367
.sort((a, b) => a.dateLabel.localeCompare(b.dateLabel));
6468
};
69+
70+
export const countTeeByDate = (
71+
userSubmissions: Submission[],
72+
problemModelsMap?: Map<string, ProblemModel>
73+
) => {
74+
const getTeeFromSubmission = (problemId: string): number => {
75+
const detail = problemModelsMap?.get(problemId);
76+
if (isProblemModelWithTimeModel(detail)) {
77+
return calculateTopPlayerEquivalentEffort(detail);
78+
} else {
79+
return 0;
80+
}
81+
};
82+
83+
const submissionMap = new Map<ProblemId, Submission>();
84+
userSubmissions
85+
.filter((s) => isAccepted(s.result))
86+
.forEach((submission) => {
87+
const current = submissionMap.get(submission.problem_id);
88+
if (current) {
89+
if (current.id > submission.id) {
90+
submissionMap.set(submission.problem_id, submission);
91+
}
92+
} else {
93+
submissionMap.set(submission.problem_id, submission);
94+
}
95+
});
96+
97+
const dailyTeeCount = Array.from(submissionMap)
98+
.map(([k, v]) => {
99+
return {
100+
date: formatMomentDate(parseSecond(v.epoch_second)),
101+
tee: getTeeFromSubmission(k),
102+
};
103+
})
104+
.reduce((map, o) => {
105+
const count = map.get(o.date) ?? 0;
106+
map.set(o.date, count + o.tee);
107+
return map;
108+
}, new Map<string, number>());
109+
110+
return Array.from(dailyTeeCount)
111+
.map(([dateLabel, count]) => ({ dateLabel, count }))
112+
.sort((a, b) => a.dateLabel.localeCompare(b.dateLabel));
113+
};

0 commit comments

Comments
 (0)