Compare commits

..

No commits in common. "d6e5d0334c06811a37722012c64db5a1ce954f28" and "978aafc204a2f28257480f5058cdec8a756d9a18" have entirely different histories.

6 changed files with 6 additions and 140 deletions

View File

@ -6,9 +6,8 @@ from fastapi.responses import JSONResponse
from pydantic import BaseModel, Field from pydantic import BaseModel, Field
from sqlmodel import Session, func, select from sqlmodel import Session, func, select
from sqlmodel.sql.expression import SelectOfScalar from sqlmodel.sql.expression import SelectOfScalar
from db import Chemistry, MVPRanking, Player, engine from db import Chemistry, Player, engine
import networkx as nx import networkx as nx
import numpy as np
import matplotlib import matplotlib
matplotlib.use("agg") matplotlib.use("agg")
@ -19,7 +18,6 @@ analysis_router = APIRouter(prefix="/analysis")
C = Chemistry C = Chemistry
R = MVPRanking
P = Player P = Player
@ -194,31 +192,9 @@ async def render_sociogram(params: Params):
return {"image": encoded_image} return {"image": encoded_image}
def mvp():
ranks = dict()
with Session(engine) as session:
subquery = (
select(R.user, func.max(R.time).label("latest"))
.where(R.time > datetime(2025, 2, 8))
.group_by(R.user)
.subquery()
)
statement2 = select(R).join(
subquery, (R.user == subquery.c.user) & (R.time == subquery.c.latest)
)
for r in session.exec(statement2):
for i, p in enumerate(r.mvps):
ranks[p] = ranks.get(p, []) + [i + 1]
return [
{"name": p, "rank": f"{np.mean(v):.02f}", "std": f"{np.std(v):.02f}"}
for p, v in ranks.items()
]
analysis_router.add_api_route("/json", endpoint=sociogram_json, methods=["GET"]) analysis_router.add_api_route("/json", endpoint=sociogram_json, methods=["GET"])
analysis_router.add_api_route("/graph_json", endpoint=graph_json, methods=["GET"]) analysis_router.add_api_route("/graph_json", endpoint=graph_json, methods=["GET"])
analysis_router.add_api_route("/image", endpoint=render_sociogram, methods=["POST"]) analysis_router.add_api_route("/image", endpoint=render_sociogram, methods=["POST"])
analysis_router.add_api_route("/mvp", endpoint=mvp, methods=["GET"])
if __name__ == "__main__": if __name__ == "__main__":
with Session(engine) as session: with Session(engine) as session:

View File

@ -10,16 +10,16 @@
"preview": "vite preview" "preview": "vite preview"
}, },
"dependencies": { "dependencies": {
"react": "18.3.1", "react": "^18.3.1",
"react-dom": "18.3.1", "react-dom": "^18.3.1",
"react-sortablejs": "^6.1.4", "react-sortablejs": "^6.1.4",
"reagraph": "^4.21.2", "reagraph": "^4.21.2",
"sortablejs": "^1.15.6" "sortablejs": "^1.15.6"
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "^9.17.0", "@eslint/js": "^9.17.0",
"@types/react": "18.3.18", "@types/react": "^18.3.18",
"@types/react-dom": "18.3.5", "@types/react-dom": "^18.3.5",
"@types/sortablejs": "^1.15.8", "@types/sortablejs": "^1.15.8",
"@vitejs/plugin-react": "^4.3.4", "@vitejs/plugin-react": "^4.3.4",
"eslint": "^9.17.0", "eslint": "^9.17.0",

View File

@ -42,7 +42,7 @@ interface DeferredProps {
} }
let timeoutID: NodeJS.Timeout | null = null; let timeoutID: number | null = null;
export default function Analysis() { export default function Analysis() {
const [image, setImage] = useState(""); const [image, setImage] = useState("");
const [params, setParams] = useState<Params>({ const [params, setParams] = useState<Params>({

View File

@ -6,7 +6,6 @@ import Rankings from "./Rankings";
import { BrowserRouter, Routes, Route } from "react-router"; import { BrowserRouter, Routes, Route } from "react-router";
import { SessionProvider } from "./Session"; import { SessionProvider } from "./Session";
import { GraphComponent } from "./Network"; import { GraphComponent } from "./Network";
import MVPChart from "./MVPChart";
function App() { function App() {
return ( return (
@ -14,25 +13,16 @@ function App() {
<Header /> <Header />
<Routes> <Routes>
<Route index element={<Rankings />} /> <Route index element={<Rankings />} />
<Route path="/network" element={ <Route path="/network" element={
<SessionProvider> <SessionProvider>
<GraphComponent /> <GraphComponent />
</SessionProvider> </SessionProvider>
} /> } />
<Route path="/analysis" element={ <Route path="/analysis" element={
<SessionProvider> <SessionProvider>
<Analysis /> <Analysis />
</SessionProvider> </SessionProvider>
} /> } />
<Route path="/mvp" element={
<SessionProvider>
<MVPChart />
</SessionProvider>
} />
</Routes> </Routes>
<Footer /> <Footer />
</BrowserRouter> </BrowserRouter>

View File

@ -1,29 +0,0 @@
import { useEffect, useState } from "react";
import { apiAuth } from "./api";
import BarChart from "./BarChart";
import { PlayerRanking } from "./types";
import RaceChart from "./RaceChart";
const MVPChart = () => {
const [data, setData] = useState({} as PlayerRanking[]);
const [loading, setLoading] = useState(true);
const [showStd, setShowStd] = useState(false);
async function loadData() {
setLoading(true);
await apiAuth("analysis/mvp", null)
.then(json => json as Promise<PlayerRanking[]>).then(json => { setData(json.sort((a, b) => a.rank - b.rank)) })
setLoading(false);
}
useEffect(() => { loadData() }, [])
return (
<>
{loading ? <span className="loader" /> : <RaceChart std={showStd} players={data} />
}
</>)
}
export default MVPChart;

View File

@ -1,71 +0,0 @@
import { FC, useEffect, useState } from 'react';
import { PlayerRanking } from './types';
interface RaceChartProps {
players: PlayerRanking[];
std: boolean;
}
const determineNiceWidth = (width: number) => {
const max = 1080;
if (width >= max)
return max
else if (width > 768)
return width * 0.8
else
return width * 0.96
}
const RaceChart: FC<RaceChartProps> = ({ players, std }) => {
// State to store window's width and height
const [width, setWidth] = useState(determineNiceWidth(window.innerWidth));
const [height, setHeight] = useState(window.innerHeight);
// Update state on resize
useEffect(() => {
const handleResize = () => {
setWidth(determineNiceWidth(window.innerWidth));
setHeight(window.innerHeight);
};
window.addEventListener('resize', handleResize);
return () => {
window.removeEventListener('resize', handleResize);
};
}, []);
const padding = 24;
const gap = 8
const maxValue = Math.max(...players.map((player) => player.rank)) + 1;
const barHeight = (height - 2 * padding) / players.length;
return (
<svg width={width} height={height}>
{players.map((player, index) => (
<rect
key={index}
x={0}
y={index * barHeight + padding}
width={(1 - player.rank / maxValue) * width}
height={barHeight - gap} // subtract 2 for some spacing between bars
fill="#36c"
/>))}
{players.map((player, index) => (
<text
key={index}
x={4}
y={index * barHeight + barHeight / 2 + padding + gap / 2}
width={(1 - player.rank / maxValue) * width}
height={barHeight - 8} // subtract 2 for some spacing between bars
fontSize="24px"
fill="aliceblue"
stroke='#36c'
strokeWidth={0.8}
fontWeight={"bold"}
>{player.name}</text>
))}
</svg>
)
}
export default RaceChart;