0% found this document useful (0 votes)
20 views

Copy Code

Uploaded by

swapnil saxena
Copyright
© © All Rights Reserved
Available Formats
Download as TXT, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
20 views

Copy Code

Uploaded by

swapnil saxena
Copyright
© © All Rights Reserved
Available Formats
Download as TXT, PDF, TXT or read online on Scribd
You are on page 1/ 17

<!

DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Codeforces Profile Analyzer</title>
</head>
<body>
<div id="root"></div>
</body>
</html>
import axios from "axios";

const apiInstance = () => {


const apiClient = axios.create({
baseURL: "https://codeforces.com/api",
});

return apiClient;
};

const apiClient = apiInstance();

export default apiClient;


import { useEffect, useState } from "react";
import Chart from "react-google-charts";

const BarChartContainer = ({ data: parentData, getData, axisTitle }) => {


const [data, setData] = useState();

useEffect(() => {
if (parentData) {
setData([axisTitle, ...getData(parentData)]);
}
}, [parentData, getData, axisTitle]);

return data ? <Chart chartType="Bar" data={data} /> : <></>;


};

export default BarChartContainer;


.react-calendar-heatmap text {
font-size: 10px;
fill: #aaa;
}

.react-calendar-heatmap rect:hover {
stroke: #555;
stroke-width: 1px;
}

.react-calendar-heatmap .color-empty {
fill: #eeeeee;
}

.react-calendar-heatmap .color-1 {
fill: #8cc665;
}
.react-calendar-heatmap .color-2 {
fill: #44a340;
}

.react-calendar-heatmap .color-3 {
fill: #1e6823;
}
import { useEffect, useState } from "react";
import CalendarHeatmap from "react-calendar-heatmap";
import moment from "moment";
import "./index.css";
import ReactTooltip from "react-tooltip";

const CalendarHeatMap = ({ data: parentData, getData, year }) => {


const [data, setData] = useState();
const [startDate, setStartDate] = useState(
moment(new Date()).subtract(1, "years").toDate()
);
const [endDate, setEndDate] = useState(new Date());

useEffect(() => {
if (year) {
const start_date = moment(`01-01-${year}`).toDate();
const end_date = moment(`12-31-${year}`).toDate();

console.log("Start date: ", start_date);


console.log("End date: ", end_date);

setStartDate(start_date);
setEndDate(end_date);
} else {
setStartDate(moment(new Date()).subtract(1, "years").toDate());
setEndDate(new Date());
}
}, [year]);

useEffect(() => {
if (parentData) {
setData(getData(parentData));
}
}, [parentData, getData]);

return data ? (
<>
<CalendarHeatmap
startDate={startDate}
endDate={endDate}
values={data}
classForValue={(value) => {
if (!value || value < 1) {
return "color-empty";
}
return `color-${value.count}`;
}}
showWeekdayLabels={true}
tooltipDataAttrs={(value) => {
if (value.date) {
return {
"data-tip": `${
value.numberOfSubmissions || 0
} submissions on ${moment(value.date).format("DD/MM/YYYY")}`,
};
}
}}
/>
<ReactTooltip />
</>
) : (
<></>
);
};

export default CalendarHeatMap;


.MuiTableRow-head {
background-color: #1769aa;
}

.MuiTableCell-head {
color: #ffffff !important;
}
import Table from "@mui/material/Table";
import TableBody from "@mui/material/TableBody";
import TableCell from "@mui/material/TableCell";
import TableContainer from "@mui/material/TableContainer";
import TableHead from "@mui/material/TableHead";
import TableRow from "@mui/material/TableRow";
import Paper from "@mui/material/Paper";
import Link from "@mui/material/Link";

import "./ContestTable.css";

export const ContestTable = ({ children: contestsList }) => {


return (
<TableContainer component={Paper}>
<Table sx={{ minWidth: 650 }} aria-label="simple table">
<TableHead>
<TableRow>
<TableCell>Name</TableCell>
<TableCell align="right">Start Date</TableCell>
<TableCell align="right">End Date</TableCell>
<TableCell align="right">Start Time</TableCell>
<TableCell align="right">End Time</TableCell>
<TableCell align="right">Duration</TableCell>
</TableRow>
</TableHead>
<TableBody>
{contestsList.map((value, index) => (
<TableRow
key={index}
sx={{ "&:last-child td, &:last-child th": { border: 0 } }}
>
<TableCell>
<Link href={value.url} target="_blank">
{value.name}
</Link>
</TableCell>
<TableCell align="right">{value.start_date}</TableCell>
<TableCell align="right">{value.end_date}</TableCell>
<TableCell align="right">{value.start_time}</TableCell>
<TableCell align="right">{value.end_time}</TableCell>
<TableCell align="right">{value.duration}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
);
};
import { ContestTable } from "./ContestTable";
import { Container } from "@mui/material";

export const ContestLister = ({ contestsList }) => {


return (
<>
{contestsList?.length && (
<>
<Container
maxWidth={false}
sx={{
marginTop: 5,
}}
>
<ContestTable>{contestsList}</ContestTable>
</Container>
</>
)}
</>
);
};
import React, { useState, useEffect } from "react";
import { Chart } from "react-google-charts";
import moment from "moment";
import _ from "lodash";

const LineChart = ({ data: parentData, getData, axisTitle }) => {


const [data, setData] = useState();
const [hAxisTicks, setHAxisTicks] = useState([]);

useEffect(() => {
const sortedData = _.sortBy(parentData, ["ratingUpdateTimeSeconds"]);

const temp = [];


let curr = moment();
let last = moment();

if (sortedData.length > 0) {
last = moment.unix(sortedData[0].ratingUpdateTimeSeconds);
}

const diff = curr.diff(last, "months");

let step;

if (diff <= 12) {


step = 1;
} else if (diff <= 24) {
step = 2;
} else if (diff <= 36) {
step = 3;
} else if (diff <= 48) {
step = 4;
} else if (diff <= 60) {
step = 6;
} else {
step = 12;
}

while (curr >= last) {


const t = curr.startOf("month").unix();
const monthName = curr.format("MMM");
const year = curr.format("YYYY");
temp.push({ v: t, f: `${monthName} ${year}` });
curr = curr.subtract(step, "months");
}

setHAxisTicks(temp);
// console.log(moment().subtract(1, "months").startOf("month").format("MMM"));
}, [parentData]);

useEffect(() => {
if (parentData) {
setData([axisTitle, ...getData(parentData)]);
}
}, [parentData, getData, axisTitle]);

const options = {
curveType: "function",
legend: { position: "bottom" },
hAxis: { ticks: hAxisTicks },
pointSize: 8,
};

return data ? (
<Chart
chartType="LineChart"
width="100%"
height="350px"
data={data}
options={options}
/>
) : null;
};

export default LineChart;


import Chart from "react-google-charts";
import { useEffect, useState } from "react";

const PieChartContainer = ({ data: parentData, getData, axisTitle }) => {


const [data, setData] = useState();

useEffect(() => {
if (parentData) {
const tempData = getData(parentData);
// sort nested array by index 1 item
tempData
.sort((a, b) => {
if (a[1] < b[1]) {
return -1;
} else if (a[1] > b[1]) {
return 1;
} else {
return 0;
}
})
.reverse();
setData([axisTitle, ...tempData]);
}
}, [parentData, getData, axisTitle]);

return data ? (
<Chart
height={400}
chartType="PieChart"
options={{ pieSliceText: "none" }}
data={data}
/>
) : (
<></>
);
};

export default PieChartContainer;


import moment from "moment/moment";

export const getProblemsCount = (data) => {


const solved = new Set();
const tried = new Set();
data?.forEach((value) => {
const name = value?.problem?.name;
const verdict = value?.verdict;

if (!solved.has(name) && verdict === "OK") {


solved.add(name);
}

if (!tried.has(name)) {
tried.add(name);
}
});

return {
tried: tried.size,
solved: solved.size,
unsolved: tried.size - solved.size,
};
};

export const getUnsolvedProblems = (data) => {


const solved = new Set();
const tried = new Set();
const triedMap = {};

data?.forEach((value) => {
const name = value?.problem?.name;
const verdict = value?.verdict;
if (!solved.has(name) && verdict === "OK") {
solved.add(name);
}

if (!tried.has(name)) {
tried.add(name);
triedMap[name] = value;
}
});

const arr = [];

tried.forEach((name) => {
if (!solved.has(name)) {
const value = triedMap[name];

const contestId = value?.problem?.contestId;


const problemIndex = value?.problem?.index;

console.log(value);
arr.push({
name: `${contestId}-${problemIndex}`,
link: `https://codeforces.com/contest/${contestId}/problem/$
{problemIndex}`,
});
}
});

return arr;
};

export const getProblemRatingDistribution = (data) => {


const mp = {};
const set = new Set();
data?.forEach((value) => {
const rating = value?.problem?.rating;
const name = value?.problem?.name;
const verdict = value?.verdict;
if (rating && !set.has(name) && verdict === "OK") {
if (mp[rating]) {
mp[rating] += 1;
} else {
mp[rating] = 1;
}
set.add(name);
}
});

let arr = [];


for (let key in mp) {
arr.push([key, mp[key]]);
}

return arr;
};

export const getProblemTagDistribution = (data) => {


const mp = {};
const set = new Set();
data?.forEach((value) => {
const tags = value?.problem?.tags;
const name = value?.problem?.name;
const verdict = value?.verdict;
if (tags && !set.has(name) && verdict === "OK") {
tags.forEach((tag) => {
if (mp[tag]) {
mp[tag] += 1;
} else {
mp[tag] = 1;
}
});
set.add(name);
}
});

let arr = [];


for (let key in mp) {
arr.push([`${key}: ${mp[key]}`, mp[key]]);
}

return arr;
};

export const getDataForCalendarHeatmap = (data) => {


const mp = {};
data.forEach((value) => {
const t = value?.creationTimeSeconds;
if (t) {
const d = moment(new Date(t * 1000)).format("YYYY-MM-DD");
if (mp[d]) {
mp[d] += 1;
} else {
mp[d] = 1;
}
}
});

const arr = [];


for (let key in mp) {
let value;
if (mp[key] < 1) {
value = 0;
} else if (mp[key] < 3) {
value = 1;
} else if (mp[key] < 5) {
value = 2;
} else {
value = 3;
}
arr.push({
date: new Date(key),
count: value,
numberOfSubmissions: mp[key],
});
}

return arr;
};

export const getContestRatingChanges = (data) => {


const arr = [];
data.forEach((value) => {
arr.push([
{
v: value.ratingUpdateTimeSeconds,
f: `Date: ${moment
.unix(value.ratingUpdateTimeSeconds)
.format("DD/MM/YYYY")}`,
},
value.newRating,
]);
});

return arr;
};

export const getYearsOptions = (data) => {


const currentYear = moment().year();

if (data && data.length) {


const n = data.length;

const firstYear = moment.unix(data[n - 1].creationTimeSeconds).year();

console.log("firstYear: ", firstYear);

const options = [{ label: "Choose year" }];

for (let year = currentYear; year >= firstYear; year--) {


options.push({
label: `${year}`,
value: `${year}`,
});
}

return options;
}

return [
{ label: "Choose year" },
{ label: `${currentYear}`, value: `${currentYear}` },
];
};

function getRandomInt(max) {
return Math.floor(Math.random() * max);
}

export const getRandomProblem = (problemList) => {


if (!problemList || !problemList.length) {
return undefined;
}

const n = problemList.length;
const index = getRandomInt(n);
return problemList[index];
};
.main-container {
padding: 10px;
}

.app-heading {
text-align: center;
font-family: Arial, Helvetica, sans-serif;
}

.handle-input {
min-width: 200px;
padding: 8px;
outline: none;
}

.submit-button {
margin-left: 10px;
padding: 8px;
cursor: pointer;
border-radius: 5px;
background-color: #1769aa;
color: #ffffff;
border: 0;
font-size: 16px;
}

.error-message {
color: red;
}

.section-container {
padding: 10px;
padding-top: 2px;
background-color: white;
box-sizing: border-box;
margin: 20px 0;
box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 3px 1px -2px rgba(0, 0, 0, 0.2),
0 1px 5px 0 rgba(0, 0, 0, 0.12);
}

.unsolved-problems-container {
display: flex;
gap: 12px;
padding: 5px 10px;
flex-wrap: wrap;
}

a {
text-decoration: none;
color: blue;
}

.problemCardWrapper {
display: flex;
justify-content: center;
padding: 10px 0;
}
.problemCard {
padding: 20px;
cursor: pointer;
font-size: 18px;
display: flex;
gap: 20px;
}

.problemCard:hover {
background-color: lightgray;
}
import { useEffect, useState } from "react";
import apiClient from "./apis/apiClient";
import BarChart from "./components/BarChart";
import CalendarHeatMap from "./components/CalendarHeatMap";
import PieChart from "./components/PieChart";
import LineChart from "./components/LineChart";
import {
getProblemRatingDistribution,
getProblemTagDistribution,
getDataForCalendarHeatmap,
getContestRatingChanges,
getProblemsCount,
getUnsolvedProblems,
getYearsOptions,
getRandomProblem,
} from "./utils";

import { ToastContainer, toast } from "react-toastify";


import "react-toastify/dist/ReactToastify.css";

import "./App.css";

import ReactLoading from "react-loading";


import { Container } from "@mui/material";

import { ContestLister } from "./components/ContestsLister";

import Select from "react-select";

import axios from "axios";

const makeTwoDigit = (d) => {


if (d < 10) {
return `0${d}`;
}

return d;
};

const App = () => {


const [username, setUsername] = useState("");
const [data, setData] = useState();
const [loading, setLoading] = useState(false);
const [contestData, setContestData] = useState();
const [problemsCount, setProblemsCount] = useState(0);
const [unsolvedProblemsList, setUnsolvedProblemsList] = useState([]);
const [yearOptions, setYearOptions] = useState([]);
const [year, setYear] = useState({ label: "Choose Year" });
const [problemList, setProblemList] = useState([]);
const [problem, setProblem] = useState();
const [contestsList, setContestsList] = useState([]);

const fetchUserInfo = async () => {


setData(null);
setLoading(true);
try {
let response = await apiClient.get(`/user.status?handle=${username}`);
setData(response?.data?.result);

response = await apiClient.get(`/user.rating?handle=${username}`);


setContestData(response?.data?.result);
} catch (error) {
console.log("Error: ", error);
if (error?.response?.status === 400) {
toast("Invalid Codeforces Handle");
} else {
toast("Something went wrong");
}
}
setLoading(false);
};

useEffect(() => {
if (data) {
setProblemsCount(getProblemsCount(data));
setUnsolvedProblemsList(getUnsolvedProblems(data));
setYearOptions(getYearsOptions(data));
}
}, [data]);

const getProblemList = async () => {


try {
const response = await apiClient.get("/problemset.problems");
setProblemList(response?.data?.result?.problems);
} catch (error) {
console.log(error);
}
};

useEffect(() => {
getProblemList();
}, []);

useEffect(() => {
console.log("Problem list: ", problemList);
const randomProblem = getRandomProblem(problemList);
setProblem(randomProblem);
console.log("Random problem: ", randomProblem);
}, [problemList]);

const fetchContestsList = async () => {


const site = "codeforces";
const res = await axios.get(`https://kontests.net/api/v1/${site}`);
let data = res.data;
data.sort((a, b) => new Date(a.start_time) - new Date(b.start_time));
data = data.map((value) => {
let d = parseInt(value.duration) / 3600;
if (d >= 24) {
d = parseInt(d / 24);
d = `${d} days`;
} else {
if (d % 0.5 !== 0) {
d = d.toFixed(2);
}
d = `${d} hours`;
}

let s = value.site;
if (!value.site) {
s = site
.split("_")
.map((txt) => txt.charAt(0).toUpperCase() + txt.slice(1))
.join("");
}

let t = new Date(value.start_time);


const start_date = `${makeTwoDigit(t.getDate())}-${makeTwoDigit(
t.getMonth() + 1
)}-${t.getFullYear()}`;
const start_time = `${makeTwoDigit(t.getHours())}:${makeTwoDigit(
t.getMinutes()
)}`;

t = new Date(value.end_time);
const end_date = `${makeTwoDigit(t.getDate())}-${makeTwoDigit(
t.getMonth() + 1
)}-${t.getFullYear()}`;
const end_time = `${makeTwoDigit(t.getHours())}:${makeTwoDigit(
t.getMinutes()
)}`;

value = {
...value,
duration: d,
site: s,
start_date: start_date,
start_time: start_time,
end_date: end_date,
end_time: end_time,
};
return value;
});
setContestsList(data);
};

useEffect(() => {
fetchContestsList();
}, []);

const displayProblem = (problem) => {


if (!problem) {
return <></>;
}

const link = `https://codeforces.com/problemset/problem/${problem?.contestId}/$


{problem?.index}`;

return (
<div className="problemCardWrapper">
<div
className="problemCard"
onClick={() => {
window.location = link;
}}
>
<span>
{problem?.contestId}-{problem?.index}
</span>
<span>{problem.name}</span>
<span>{problem.rating}</span>
</div>
</div>
);
};

return (
<>
<div className="main-container">
<h2 className="app-heading">Codeforces Profile Analyzer</h2>
<input
className="handle-input"
type="text"
onChange={(e) => setUsername(e.target.value)}
placeholder="Enter codeforces handle"
/>
<button className="submit-button" onClick={fetchUserInfo}>
Search
</button>
{loading ? (
<Container
sx={{
display: "flex",
justifyContent: "center",
paddingTop: "200px",
}}
>
<ReactLoading type="spin" color="#000000" height={60} width={60} />
</Container>
) : (
data && (
<div style={{ marginTop: 20 }}>
<div className="section-container">
<h3>Solved Problem's Rating Distribution</h3>

<BarChart
getData={getProblemRatingDistribution}
data={data}
axisTitle={["Problem Rating", "Solved Count"]}
/>
</div>

<div className="section-container">
<h3>Solved Problem's Tags Distribution</h3>
<PieChart
getData={getProblemTagDistribution}
data={data}
axisTitle={["Problem Tag", "Solved Count"]}
/>
</div>

<div className="section-container">
<h3>User contest rating change</h3>

<LineChart
data={contestData}
getData={getContestRatingChanges}
axisTitle={["Time", "Rating"]}
/>
</div>

<div className="section-container">
<h3>Stats</h3>

<p>Number of contests: {contestData.length}</p>


<p>Number of problems tried: {problemsCount.tried}</p>
<p>Number of problems solved: {problemsCount.solved}</p>
<p>Number of problems unsolved: {problemsCount.unsolved}</p>
</div>

{unsolvedProblemsList.length && (
<div className="section-container">
<h3>Unsolved Problems</h3>

<div className="unsolved-problems-container">
{unsolvedProblemsList.map((value) => (
<p>
<a href={value.link} target="_blank">
{value.name}
</a>
</p>
))}
</div>
</div>
)}

<div className="section-container">
<div
style={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
}}
>
<h3>User Activity Heat Map</h3>

<div
style={{
width: 180,
}}
>
<Select
options={yearOptions}
onChange={(option) => {
setYear(option);
}}
value={year}
/>
</div>
</div>

<CalendarHeatMap
getData={getDataForCalendarHeatmap}
data={data}
year={year.value}
/>
</div>

<div className="section-container">
<div
style={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
}}
>
<h3>Random problem</h3>

<button
className="submit-button"
onClick={() => {
setProblem(getRandomProblem(problemList));
}}
>
Search Problem
</button>
</div>

{displayProblem(problem)}
</div>

<div className="section-container">
<h3>Current or Upcoming Contests</h3>

<ContestLister contestsList={contestsList} />


</div>
</div>
)
)}
</div>

<ToastContainer />
</>
);
};

export default App;


* {
font-family: Arial, Helvetica, sans-serif;
}

body {
background-color: #f5f5f5;
}

body::-webkit-scrollbar {
background: white;
width: 8px;
}

body::-webkit-scrollbar-thumb {
background: rgb(118, 118, 118);
border-radius: 4px;
}
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";

import "./index.css";

const root = ReactDOM.createRoot(document.getElementById("root"));


root.render(<App />);

You might also like