Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Frontend fixes #103

Merged
merged 11 commits into from
Dec 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions frontend/app/components/charts/lineChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@ import * as Highcharts from 'highcharts';
import HighchartsReact from "highcharts-react-official";
import {useRef} from "react";
import {HeroColors} from "@/app/components/charts/heroColors";
import type {TrendLine} from "@/pages/trends";

import type {TrendLine} from "@/app/utils/serverSideProps";


interface LineChartProps extends HighchartsReact.Props {
Expand Down
8 changes: 7 additions & 1 deletion frontend/app/components/header/header.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,18 @@

.navbar {
@apply bg-gray-300 w-full overflow-x-auto flex whitespace-nowrap;
scrollbar-width: none;
-ms-overflow-style: none;

ul {
@apply flex list-none space-x-4;
}

ul > li {
@apply p-3 hover:bg-gray-400 transition duration-100 ease-in-out;
@apply hover:bg-gray-400 transition duration-100 ease-in-out;
}
}

.navbar::-webkit-scrollbar{
display: none;
}
56 changes: 46 additions & 10 deletions frontend/app/components/header/header.tsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,64 @@
"use client";
import {useEffect, useState} from "react";
import styles from "./header.module.scss"
import {fetchSeasonList} from "@/app/utils/clientSideFetch";
import {useRouter} from "next/router";


export type NavLinks = {
type NavLinks = {
label: string;
path: string;
path: string
}

export type HeaderProps = {
nav_links: NavLinks[];
}
const Header = () => {
const [navLinks, setNavLinks] = useState<NavLinks[]>([])
const router = useRouter();
useEffect(() => {
fetchSeasonList().then(seasonList => {
setNavLinks(seasonList.reverse().map(season => {
const seasonNumber = season.split("_")[0]
return {label: `Season ${seasonNumber}`, path: `/season/${seasonNumber}`}
}))
if (router.pathname === "/" && typeof window !== undefined) {
const element = document.getElementById("curseason")
if (element) {
element.innerText = `Season ${seasonList[0].split("_")[0]}`
}
}

if (typeof window !== undefined){
const element = document.getElementById('navbar')
if (element){
element.addEventListener('wheel', function(e) {
// @ts-ignore
if (e.deltaY != 0) {
// @ts-ignore
this.scrollLeft += (e.deltaY * 1.5);
e.preventDefault();
}
}, false);
}

}

})

}, []);


const Header = ({nav_links}: HeaderProps) => {
return (
<header>
<div className={styles.top_header_container}>
<h1>Top 500 Aggregator</h1>
</div>

<div className={styles.navbar}>
<div className={styles.navbar} id="navbar">
<ul>
{nav_links.map(link => {
return <li><a href={link.path}>{link.label}</a></li>
{!!navLinks.length && navLinks.map(link => {
return <li><a className="block p-2 text-inherit no-underline" href={link.path}>{link.label}</a></li>
})}

{!navLinks.length && <li><p className="invisible">you're not supposed to see this...</p></li>}

</ul>
</div>

Expand Down
6 changes: 6 additions & 0 deletions frontend/app/utils/clientSideFetch.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@

export async function fetchSeasonList(): Promise<string[]> {
const response = await fetch("/api/d/seasons")
return await response.json()
}

58 changes: 58 additions & 0 deletions frontend/app/utils/serverSideProps.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
const backendUrl = process.env.BACKEND_URL;
export type Statistic = {
mean: number;
standard_deviation: number;
variance: number;
}

export type BarChartData = {
labels: string[];
values: number[];
}

export type SingleChart = {
graph: BarChartData;
statistic: Statistic;
}

export type SeasonData = {
[key: string]: SingleChart
}

export type StdDevs = {
DAMAGE: number;
SUPPORT: number;
TANK: number;
}

export type TrendLine = {
name: string;
data: number[]
}

export async function fetchSingleSeasonPageChartData(seasonNumber: number): Promise<SingleChart[]> {
const response = await fetch(`${backendUrl}/chart/${seasonNumber}_8`);
return await response.json();
}


export async function fetchSeasonList(): Promise<string[]> {
const response = await fetch(`${backendUrl}/d/seasons`)
return await response.json()
}


export async function fetchSingleSeasonStdDevs(seasonNumber: number): Promise<StdDevs> {
const response = await fetch(`${backendUrl}/d/single_season_std_by_role/${seasonNumber}_8`)
return await response.json()
}

export async function fetchSeasonalOccurrenceTrend(): Promise<TrendLine[]>{
const response = await fetch(`${backendUrl}/chart/trend/d`)
return await response.json()
}

export async function fetchSeasonalStdDevTrendByRole(): Promise<TrendLine[]>{
const response = await fetch(`${backendUrl}/d/all_seasons_std_by_role`)
return await response.json()
}
16 changes: 15 additions & 1 deletion frontend/next.config.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,18 @@
/** @type {import('next').NextConfig} */
const nextConfig = {}
const backendUrl = process.env.NODE_ENV === "development" ? "http://localhost:7771" : "http://server:8000"

const nextConfig = {
async rewrites() {
return [
{
source: "/api/:path*",
destination: `${backendUrl}/:path*`
}
]
},
env: {
BACKEND_URL: backendUrl
}
}

module.exports = nextConfig
20 changes: 3 additions & 17 deletions frontend/pages/_app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import '../app/globals.scss';
import type {AppProps} from 'next/app';
import {Header} from "@/app/components";
import {useRouter} from "next/router";
import {GetServerSidePropsContext} from "next";

interface T500AggregatorAppProps extends AppProps {
data: string[] | undefined;
Expand All @@ -14,25 +13,12 @@ function MyApp({Component, pageProps}: T500AggregatorAppProps) {
const router = useRouter()
const {seasonNumber} = router.query
const path = router.asPath
const links = [
{label: "Trends", path: "/trends"},
{label: "Season 7", path: "/season/7"},
{label: "Season 6", path: "/season/6"},
{label: "Season 5", path: "/season/5"},
{label: "Season 4", path: "/season/4"},
{label: "Season 3", path: "/season/3"},
{label: "Season 2", path: "/season/2"},
{label: "Season 1", path: "/season/1"},
{label: "Season 36", path: "/season/36"},
{label: "Season 35", path: "/season/35"},
{label: "Season 34", path: "/season/34"},
]

return (
<>
<Header nav_links={links}/>
<Header />
<main className="mt-5 lg:ml-3 lg:mr-3 sm:ml-1 sm:mr-1">
<h2 className={"text-4xl pb-4"}>{seasonNumber? `Season ${seasonNumber}`: path.toLowerCase().includes("trends") ? "Trends" : "Season 7"}</h2>

<h1 id="curseason" className={"text-4xl pb-4"}>{seasonNumber ?`Season ${seasonNumber}` : path.toLowerCase().includes("trends") ? "Trends": null}</h1>
<p className="pb-2"><strong>Welcome to Overwatch 2 Top 500 Aggregator</strong></p>
<p>The data available on this page is not 100% accurate. Data collection involves computer vision and
image classification using a neural network, and as
Expand Down
128 changes: 17 additions & 111 deletions frontend/pages/index.tsx
Original file line number Diff line number Diff line change
@@ -1,126 +1,32 @@
import {Card} from "@/app/components";
import {BarChart} from "@/app/components";
import Season from "@/pages/season/[seasonNumber]";
import {
fetchSeasonList,
fetchSingleSeasonPageChartData,
fetchSingleSeasonStdDevs,
SeasonData,
StdDevs
} from "@/app/utils/serverSideProps";
import {GetServerSidePropsContext} from "next";

type Statistic = {
mean: number;
standard_deviation: number;
variance: number;
}

type BarChartData = {
labels: string[];
values: number[];
}

type SingleChart = {
graph: BarChartData;
statistic: Statistic;
}

type SeasonData = {
[key: string]: SingleChart;
}

type StdDevs = {
DAMAGE: number;
SUPPORT: number;
TANK: number;
}


const HeroStdDev = ({value, role}: {value: number, role: string}) => {
return (
<div className="text-center pt-5 pb-5">
<h5>{role}</h5>
<h6 className="text-lg text-center">{Math.round((value + Number.EPSILON) * 100) / 100}</h6>

</div>
)
}

const Index = ({data, season_list, std_devs}: { data: SeasonData, season_list: string[], std_devs: StdDevs}) => {
return (
<>

<Card title="Role Standard Deviation: All Slots, All Regions" subtitle={"Note: The standard deviation is calculated with the 10th percentile excluded. T500 aggregator by nature skews the accuracy of the low outliers in a data set. For this reason, the bottom 10% of entries for any given set (support, damage or tank) is excluded from the calculation."}>
<HeroStdDev value={std_devs.SUPPORT} role={"SUPPORT"} />
<HeroStdDev value={std_devs.DAMAGE} role={"DAMAGE"}/>
<HeroStdDev value={std_devs.TANK} role={"TANK"}/>
</Card>


<Card title="Hero Occurrences: All Slots" nowrap>
{Object.keys(data).map(key => {
if (key.includes("O_ALL")){
const [_, role, region] = key.split("_")
return <BarChart title={`${region}`} graph={data[key].graph} maxY={region === "ALL" ? 1250 : 500 } />
}
})}
</Card>



<Card title="Hero Occurrences: First Most Played">
{Object.keys(data).map(key => {
if (key.includes("OFMP")){
const [_, role, region] = key.split("_")
return <BarChart title={`${role}: ${region}`} graph={data[key].graph} maxY={region === "ALL" ? 500 : 300 } />
}
})}

</Card>

<Card title="Hero Occurrences: Second Most Played">
{Object.keys(data).map(key => {
if (key.includes("OSMP")){
const [_, role, region] = key.split("_")
return <BarChart title={`${role}: ${region}`} graph={data[key].graph} maxY={region === "ALL" ? 500 : 300 } />
}
})}

</Card>


<Card title="Hero Occurrences: Third Most Played">
{Object.keys(data).map(key => {
if (key.includes("OTMP")){
const [_, role, region] = key.split("_")
return <BarChart title={`${role}: ${region}`} graph={data[key].graph} maxY={region === "ALL" ? 500 : 300 } />
}
})}

</Card>

</>
)
const Index = ({seasonChartData, seasonStdDevs}: {seasonChartData: SeasonData, seasonStdDevs: StdDevs}) => {
return <Season seasonChartData={seasonChartData} seasonStdDevs={seasonStdDevs} />
}

export async function getServerSideProps(context: GetServerSidePropsContext) {

const seasonNumber = "7"

// Make an API call using seasonNumber
const res = await fetch(`http://server:8000/chart/${seasonNumber}_8`);
const data = await res.json();


const res2 = await fetch("http://server:8000/d/seasons")
const season_list = await res2.json()

const res3 = await fetch(`http://server:8000/d/single_season_std_by_role/${seasonNumber}_8`)
const std_devs = await res3.json()
// @ts-ignore
const seasonList = await fetchSeasonList()
const seasonNumber = Number(seasonList[seasonList.length -1].split("_")[0])
const seasonChartData = await fetchSingleSeasonPageChartData(seasonNumber)
const seasonStdDevs = await fetchSingleSeasonStdDevs(seasonNumber)

return {
props: {
data,
season_list,
std_devs
seasonChartData,
seasonStdDevs
},
};
}




export default Index
Loading