Committing to save - incomplete implementation of graphing
This commit is contained in:
parent
105dc438bd
commit
e263683ce6
@ -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
59
app/routers/stats.py
Normal 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")
|
@ -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 {
|
||||
|
@ -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');
|
||||
});
|
||||
})
|
39
app/static/js/stats/graph.js
Normal file
39
app/static/js/stats/graph.js
Normal 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'
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
});
|
@ -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>
|
||||
|
15
app/templates/stats/graph.html
Normal file
15
app/templates/stats/graph.html
Normal 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 %}
|
Loading…
x
Reference in New Issue
Block a user