merge: Optimize graphs (#6)

* minor changes
* Make more minimalist
* add changes requested by client

* refactor: move graph gen to client, add filters

Backend:
- Change /graph endpoint to return raw branch data instead of full Plotly config
- Remove plotly dependency from backend
- Return JSON: {branches: [{name, years, marks}, ...]}

Frontend:
- Build Plotly traces client-side from raw data
- Add degree filter (All/B.E./M.Sc.)
- Add Phoenix branches filter
- Fix CORS URL configuration
- Remove problematic animation causing flashing

* fix any pending issues

---------

Co-authored-by: pranav <pranavu8406@gmail.com>
This commit is contained in:
vee1e 2026-02-14 21:05:33 +05:30 committed by GitHub
parent 21329e0177
commit 56bd4ca53e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 88 additions and 52 deletions

View file

@ -1,19 +1,16 @@
from fastapi import FastAPI, Query, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from dotenv import load_dotenv
import plotly.graph_objects as go
import pandas as pd
import csv
import os
load_dotenv()
CORS_ORIGINS = [
origin for origin in os.getenv("CORS_ORIGINS", "").split(",") if origin
]
CORS_ORIGINS = [origin for origin in os.getenv("CORS_ORIGINS", "").split(",") if origin]
csvFiles = ["best_case.csv", "most_likely_case.csv", "worst_case.csv"]
campusDict = {"Pilani":0, "Goa":1, "Hyderabad":2 }
campusDict = {"Pilani": 0, "Goa": 1, "Hyderabad": 2}
campusArr = ["Pilani", "Goa", "Hyderabad"]
dfs = []
tableData = [[] for _ in range(9)]
@ -35,12 +32,12 @@ df["marks"] = df["marks"].astype(int)
for case_idx in range(3):
path = "predictions/" + csvFiles[case_idx]
try:
with open(path, newline='', encoding='utf-8') as csv_file:
csv_reader = csv.reader(csv_file, delimiter=',')
with open(path, newline="", encoding="utf-8") as csv_file:
csv_reader = csv.reader(csv_file, delimiter=",")
for row in csv_reader:
if row[0] in campusDict:
campus_idx = campusDict[row[0]]
tableData[campus_idx*3+case_idx].append(row)
tableData[campus_idx * 3 + case_idx].append(row)
except FileNotFoundError:
print(f"Prediction file {path} not found, skipping.")
@ -53,66 +50,42 @@ app.add_middleware(
allow_origins=origins,
allow_credentials=False,
allow_methods=["*"],
allow_headers=["*"]
allow_headers=["*"],
)
def get_df_by_campus(campus: int = Query(0, ge=0, le=2)):
def get_df_by_campus(campus: int = 0):
try:
campus_name = campusArr[campus]
except IndexError:
raise HTTPException(status_code=400, detail="Invalid Campus Index")
ndf = df[df["campus"]==campus_name].iloc[:, -3:]
ndf = df[df["campus"] == campus_name][["year", "branch", "marks"]]
return ndf
@app.get("/graph")
def get_graph(campus: int = Query(0, ge=0, le=2)):
campus_data = get_df_by_campus(campus)
fig = go.Figure()
branches = []
for branch, values in campus_data.groupby("branch"):
fig.add_trace(go.Scatter(
x=values["year"],
y=values["marks"],
mode="lines+markers",
name=branch
branches.append(
{
"name": branch,
"years": values["year"].tolist(),
"marks": values["marks"].tolist(),
}
)
)
fig.update_layout(
template="plotly_dark",
legend_title="Branches",
hovermode="x",
xaxis=dict(
showspikes=True,
spikemode="across",
spikesnap="cursor",
spikethickness=1,
spikecolor="rgba(255,255,255,0.6)"
),
margin=dict(l=20, r=20, t=30, b=30)
)
fig.update_xaxes(
tickmode="linear",
dtick=1,
title="Year"
)
fig.update_yaxes(
title="Marks"
)
return fig.to_dict()
return {"branches": branches}
@app.get("/table")
def get_table(campus: int = Query(0, ge=0, le=2), scenario: int = Query(0, ge=0, le=2)):
try:
tableRows = tableData[campus*3+scenario]
tableRows = tableData[campus * 3 + scenario]
except IndexError:
raise HTTPException(400, "Invalid Scenario or Campus index")
return tableRows

View file

@ -4,8 +4,28 @@ import Plotly from "plotly.js-basic-dist";
import createPlotlyComponent from "react-plotly.js/factory";
import { useTheme } from "@/lib/themeContext";
interface BranchData {
name: string;
years: number[];
marks: number[];
}
interface GraphResponse {
branches: BranchData[];
}
interface Trace {
x: number[];
y: number[];
name: string;
type: 'scatter';
mode: 'lines+markers';
line?: { color?: string; width?: number };
marker?: { size?: number; line?: { width?: number; color?: string } };
}
interface PlotParams {
data?: any[];
data?: Trace[];
layout?: any;
}
@ -27,6 +47,29 @@ const formConfig = [
},
] as const;
const PHOENIX_PATTERNS = [
/computer science/i,
/electrical/i,
/electronics/i,
/mathematics.{0,3}(computing|and)/i,
];
const MSC_PATTERNS = [
/M\.?Sc\.?/i,
];
function isBE(branchName: string): boolean {
return !MSC_PATTERNS.some((pattern) => pattern.test(branchName));
}
function isMSc(branchName: string): boolean {
return MSC_PATTERNS.some((pattern) => pattern.test(branchName));
}
function isPhoenix(branchName: string): boolean {
return PHOENIX_PATTERNS.some((pattern) => pattern.test(branchName));
}
function GraphPlot() {
const { theme } = useTheme();
const [graph, setGraph] = useState<PlotParams>({});
@ -54,8 +97,28 @@ function GraphPlot() {
try {
const res = await fetch(url);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const data = await res.json();
setGraph(data);
const response: GraphResponse = await res.json();
const traces: Trace[] = response.branches.map((branch) => ({
x: branch.years,
y: branch.marks,
name: branch.name,
type: 'scatter',
mode: 'lines+markers',
}));
setGraph({
data: traces,
layout: {
title: {
text: 'Cutoff Trends',
font: {
family: '"JetBrains Mono", monospace',
size: isMobile ? 14 : 18,
},
},
},
});
setIsLoaded(true);
} catch (err) {
console.error("Failed to load Plot. Error: ", err);

View file

@ -1 +1 @@
{"root":["./src/about.tsx","./src/app.tsx","./src/footer.tsx","./src/header.tsx","./src/home.tsx","./src/working.tsx","./src/main.tsx","./src/react-plotly.d.ts","./src/vite-env.d.ts","./src/components/ui/dropdown.tsx","./src/components/ui/graphplot.tsx","./src/components/ui/predicttable.tsx","./src/components/ui/profilecard.tsx","./src/lib/themecontext.tsx","./src/lib/utils.tsx","./src/lib/hooks/useappseo.ts"],"version":"5.8.3"}
{"root":["./src/about.tsx","./src/app.tsx","./src/footer.tsx","./src/header.tsx","./src/home.tsx","./src/working.tsx","./src/main.tsx","./src/react-plotly.d.ts","./src/vite-env.d.ts","./src/components/ui/dropdown.tsx","./src/components/ui/graphplot.tsx","./src/components/ui/predicttable.tsx","./src/components/ui/profilecard.tsx","./src/lib/themecontext.tsx","./src/lib/utils.tsx","./src/lib/hooks/useappseo.ts"],"errors":true,"version":"5.8.3"}