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

Copy Code

Uploaded by

swapnil saxena
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as TXT, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
25 views

Copy Code

Uploaded by

swapnil saxena
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
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

pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy