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 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")
|
api_router = APIRouter(prefix="/api")
|
||||||
html_router = APIRouter()
|
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(games.api_router)
|
||||||
api_router.include_router(score.api_router)
|
api_router.include_router(score.api_router)
|
||||||
api_router.include_router(seed.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(decks.html_router)
|
||||||
html_router.include_router(players.html_router)
|
html_router.include_router(players.html_router)
|
||||||
html_router.include_router(games.html_router)
|
html_router.include_router(games.html_router)
|
||||||
html_router.include_router(seed.html_router)
|
html_router.include_router(seed.html_router)
|
||||||
|
html_router.include_router(stats.html_router)
|
||||||
|
|
||||||
html_router.include_router(base.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 {
|
.topbar_item {
|
||||||
margin: 0px 10px;
|
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 {
|
#header div#create_game_button {
|
||||||
|
@ -1,3 +1,11 @@
|
|||||||
$.ajaxSetup({
|
$.ajaxSetup({
|
||||||
contentType: "application/json; charset=utf-8"
|
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"
|
src="https://code.jquery.com/jquery-3.7.1.min.js"
|
||||||
integrity="sha256-/JqT3SQfawRcv/BIHPThkBvs0OEvtFFmqPF/lYI/Cxo="
|
integrity="sha256-/JqT3SQfawRcv/BIHPThkBvs0OEvtFFmqPF/lYI/Cxo="
|
||||||
crossorigin="anonymous"></script>
|
crossorigin="anonymous"></script>
|
||||||
|
<script src="/static/js/base.js"></script>
|
||||||
<link rel="stylesheet" href="/static/css/base.css"/>
|
<link rel="stylesheet" href="/static/css/base.css"/>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
</head>
|
</head>
|
||||||
@ -19,6 +20,12 @@
|
|||||||
<a class="topbar_item" href="/game/list">Games</a>
|
<a class="topbar_item" href="/game/list">Games</a>
|
||||||
<a class="topbar_item" href="/deck/list">Decks</a>
|
<a class="topbar_item" href="/deck/list">Decks</a>
|
||||||
<a class="topbar_item" href="/player/list">Players</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>
|
||||||
<div id="create_game_button">
|
<div id="create_game_button">
|
||||||
<a id="create_game_link" href="/game/create">Record New Game</a>
|
<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