Committing to save - incomplete implementation of graphing

This commit is contained in:
Jack Jackson 2024-07-28 23:23:11 -07:00
parent 105dc438bd
commit e263683ce6
7 changed files with 164 additions and 2 deletions

View File

@ -1,6 +1,6 @@
from fastapi import APIRouter
from . import base, decks, games, players, score, seed
from . import base, decks, games, players, score, seed, stats
api_router = APIRouter(prefix="/api")
html_router = APIRouter()
@ -10,10 +10,12 @@ api_router.include_router(players.api_router)
api_router.include_router(games.api_router)
api_router.include_router(score.api_router)
api_router.include_router(seed.api_router)
api_router.include_router(stats.api_router)
html_router.include_router(decks.html_router)
html_router.include_router(players.html_router)
html_router.include_router(games.html_router)
html_router.include_router(seed.html_router)
html_router.include_router(stats.html_router)
html_router.include_router(base.html_router)

59
app/routers/stats.py Normal file
View File

@ -0,0 +1,59 @@
from collections import defaultdict
from fastapi import APIRouter, Depends, Request
from fastapi.responses import HTMLResponse
from sqlalchemy.sql.expression import func
from app.sql import models
from ..templates import jinja_templates
from ..sql.database import get_db
api_router = APIRouter(prefix="/stats", tags=["stats"])
html_router = APIRouter(
prefix="/stats", include_in_schema=False, default_response_class=HTMLResponse
)
@api_router.get("/graph")
def stats_graph_api(db=Depends(get_db)):
# TODO - parallelize? (Probably not worth it :P )
# SO Answer on row_number: https://stackoverflow.com/a/38160409/1040915
# Docs: https://docs.sqlalchemy.org/en/20/core/sqlelement.html#sqlalchemy.sql.expression.over
row_number_column = (
func.row_number()
.over(
partition_by=[models.Deck.name, models.Game.date],
order_by=models.EloScore.id.desc(),
)
.label("row_number")
)
sub_query = (
db.query(models.Deck.name, models.EloScore.score, models.Game.date)
.outerjoin(models.EloScore, models.Deck.id == models.EloScore.deck_id)
.join(models.Game, models.EloScore.after_game_id == models.Game.id)
.add_column(row_number_column)
.subquery()
)
query = db.query(sub_query).filter(sub_query.c.row_number == 1)
results = query.all()
data_grouped_by_deck = defaultdict(list)
for result in results:
# TODO - how to index results by name instead of tuple-number
data_grouped_by_deck[result[0]].append(
{"score": result[1], "date": result[2].strftime("%Y-%m-%d")}
)
return {
"datasets": [
{"label": key, "data": data_grouped_by_deck[key]}
for key in data_grouped_by_deck
]
}
@html_router.get("/graph")
def stats_graph(request: Request, db=Depends(get_db)):
return jinja_templates.TemplateResponse(request, "stats/graph.html")

View File

@ -45,6 +45,38 @@ body {
.topbar_item {
margin: 0px 10px;
float: none !important;
display: inline;
position: relative; /* Necessary in order for dropdowns to be contained within them */
}
/* https://www.freecodecamp.org/news/how-to-build-a-dropdown-menu-with-javascript/*/
.topbar_dropdown_button {
cursor: pointer;
}
.topbar_dropdown {
visibility: hidden;
opacity: 0;
position: absolute;
top: 20px;
left: 0px;
}
.topbar_dropdown.show {
transform: translateY(0rem);
visibility: visible;
opacity: 1;
}
.topbar_dropdown a {
background-color: #0080ff;
display: block;
padding: 4px;
}
.topbar_dropdown a:last-child {
border-radius: 0px 0px 10px 10px;
}
#header div#create_game_button {

View File

@ -1,3 +1,11 @@
$.ajaxSetup({
contentType: "application/json; charset=utf-8"
});
$(document).ready(function() {
$('.topbar_dropdown_button').click(function() {
console.log('clicked')
console.log(this);
$(this).children('.topbar_dropdown').toggleClass('show');
});
})

View File

@ -0,0 +1,39 @@
$(document).ready(function() {
const data = [
{ year: 2010, count: 10 },
{ year: 2011, count: 20 },
{ year: 2012, count: 15 },
{ year: 2013, count: 25 },
{ year: 2014, count: 22 },
{ year: 2015, count: 30 },
{ year: 2016, count: 28 },
];
fetch('/api/stats/graph')
.then(response => response.json())
.then(response => {
console.log(response.datasets);
new Chart(
document.getElementById('graph_canvas'),
{
type: 'line',
data: {
datasets: response.datasets
},
options: {
scales: {
x: {
type: 'time'
}
},
parsing: {
xAxisKey: 'date',
yAxisKey: 'score'
}
}
}
);
});
});

View File

@ -7,6 +7,7 @@
src="https://code.jquery.com/jquery-3.7.1.min.js"
integrity="sha256-/JqT3SQfawRcv/BIHPThkBvs0OEvtFFmqPF/lYI/Cxo="
crossorigin="anonymous"></script>
<script src="/static/js/base.js"></script>
<link rel="stylesheet" href="/static/css/base.css"/>
{% endblock %}
</head>
@ -19,6 +20,12 @@
<a class="topbar_item" href="/game/list">Games</a>
<a class="topbar_item" href="/deck/list">Decks</a>
<a class="topbar_item" href="/player/list">Players</a>
<div class="topbar_item topbar_dropdown_button" id="stats_dropdown_button">Stats
<div class="topbar_dropdown" id="stats_dropdown">
<a href="/stats/graph">Graph</a>
</div>
</div>
</div>
<div id="create_game_button">
<a id="create_game_link" href="/game/create">Record New Game</a>

View File

@ -0,0 +1,15 @@
{% extends "base.html" %}
{% block title %}Graph{% endblock %}
{% block head %}
{{ super() }}
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.4.1/chart.umd.js" integrity="sha512-ZwR1/gSZM3ai6vCdI+LVF1zSq/5HznD3ZSTk7kajkaj4D292NLuduDCO1c/NT8Id+jE58KYLKT7hXnbtryGmMg==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://cdn.jsdelivr.net/npm/moment@2.27.0"></script>
<script src="https://cdn.jsdelivr.net/npm/chartjs-adapter-moment@0.1.1"></script>
<script src="/static/js/stats/graph.js"></script>
{% endblock %}
{% block content %}
<div id="wrapper" style="width:800px"><canvas id="graph_canvas"></canvas></div>
{% endblock %}