From 3b1c3d7eb34be38af3c766aff88f945873f0e023 Mon Sep 17 00:00:00 2001 From: Jack Jackson Date: Mon, 29 Jul 2024 20:16:01 -0700 Subject: [PATCH] Add filtering on graph --- app/routers/decks.py | 17 ++++++++++++++- app/routers/stats.py | 23 ++++++++++++++++++-- app/static/css/graph.css | 5 +++++ app/static/js/stats/graph.js | 39 +++++++++++++++++++++++++++++++++- app/templates/stats/graph.html | 6 ++++++ 5 files changed, 86 insertions(+), 4 deletions(-) create mode 100644 app/static/css/graph.css diff --git a/app/routers/decks.py b/app/routers/decks.py index 1b03ba6..75a36bf 100644 --- a/app/routers/decks.py +++ b/app/routers/decks.py @@ -1,3 +1,5 @@ +from collections import defaultdict + from fastapi import APIRouter, Depends, HTTPException, Request from fastapi.responses import HTMLResponse from sqlalchemy import and_, or_, func @@ -9,7 +11,7 @@ from ..templates import jinja_templates from ..sql import crud, schemas from ..sql.database import get_db -from .players import list_players +from .players import read_player, list_players from .games import _render_game_participants api_router = APIRouter(prefix="/deck", tags=["deck"]) @@ -23,6 +25,19 @@ html_router = APIRouter( ######## +@api_router.get("/by_player") +def decks_by_player(db=Depends(get_db)): + decks = crud.get_decks(db, skip=0, limit=-1) + # TODO - could probably do this directly in-db with some fancy use of `group_concat` + return_value = defaultdict(list) + for deck in decks: + # TODO - if we cared about latency, could cache this + return_value[read_player(deck.owner_id, db).name].append( + {"id": deck.id, "name": deck.name} + ) + return return_value + + @api_router.post("/", response_model=schemas.Deck, status_code=201) def create_deck(deck: schemas.DeckCreate, db: Session = Depends(get_db)): db_player = crud.get_player_by_id(db, deck.owner_id) diff --git a/app/routers/stats.py b/app/routers/stats.py index a06a42e..6e087cf 100644 --- a/app/routers/stats.py +++ b/app/routers/stats.py @@ -1,4 +1,5 @@ from collections import defaultdict +from datetime import datetime, MINYEAR from typing import Optional from fastapi import APIRouter, Depends, Request @@ -17,7 +18,11 @@ html_router = APIRouter( @api_router.get("/graph") -def stats_graph_api(deck_ids: Optional[str] = None, db=Depends(get_db)): +def stats_graph_api( + deck_ids: Optional[str] = None, + normalize_final_datapoint: bool = False, + db=Depends(get_db), +): # TODO - parallelize? (Probably not worth it :P ) # SO Answer on row_number: https://stackoverflow.com/a/38160409/1040915 @@ -44,12 +49,26 @@ def stats_graph_api(deck_ids: Optional[str] = None, db=Depends(get_db)): results = query.all() data_grouped_by_deck = defaultdict(list) + latest_date_so_far = datetime(MINYEAR, 1, 1, 0, 0, 0, 0) for result in results: # TODO - how to index results by name instead of tuple-number + date = result[2] + latest_date_so_far = max(latest_date_so_far, date) data_grouped_by_deck[result[0]].append( - {"score": result[1], "date": result[2].strftime("%Y-%m-%d")} + {"score": result[1], "date": date.strftime("%Y-%m-%d")} ) + if normalize_final_datapoint: + # Add a fake final datapoint to the series for any decks that weren't played in the latest game, so that lines + # continue all the way to the end of the graph + latest_date_formatted = latest_date_so_far.strftime("%Y-%m-%d") + print(f"DEBUG = {latest_date_formatted=}") + for games in data_grouped_by_deck.values(): + if games[-1]["date"] != latest_date_formatted: + games.append( + {"score": games[-1]["score"], "date": latest_date_formatted} + ) + return { "datasets": [ {"label": key, "data": data_grouped_by_deck[key]} diff --git a/app/static/css/graph.css b/app/static/css/graph.css new file mode 100644 index 0000000..627f740 --- /dev/null +++ b/app/static/css/graph.css @@ -0,0 +1,5 @@ +#options div { + float: left; + margin: 2px; + background-color: lightblue; +} \ No newline at end of file diff --git a/app/static/js/stats/graph.js b/app/static/js/stats/graph.js index 9fe961b..553c33d 100644 --- a/app/static/js/stats/graph.js +++ b/app/static/js/stats/graph.js @@ -1,9 +1,35 @@ +function updateGraphWithFilter() { + filterString = $('#options input[type=checkbox]:checked').map((idx, elem) => { + console.log('Adding ' + elem.id + ' to return string') + return elem.id + }).get().join(',') + console.log(filterString); + fetch('/api/stats/graph?deck_ids=' + filterString) + .then(response => response.json()) + .then(response => { + window.chart.data.datasets = response.datasets + window.chart.update() + }) +} + $(document).ready(function() { + + $('#filter_button').click(updateGraphWithFilter) + + fetch('/api/deck/by_player') + .then(response => response.json()) + .then(response => { + console.log(response); + for (playerName in response) { + buildPlayerDecksDiv($('#options'), playerName, response[playerName]) + } + }) + fetch('/api/stats/graph') .then(response => response.json()) .then(response => { console.log(response.datasets); - new Chart( + window.chart = new Chart( document.getElementById('graph_canvas'), { type: 'line', @@ -27,3 +53,14 @@ $(document).ready(function() { }); }); + +function buildPlayerDecksDiv(parentDiv, playerName, playerDecks) { + div = $('
', + id='player_div_for_' + playerName + ) + div.append('

' + playerName + '

') + for (deck of playerDecks) { + div.append('
') + } + div.appendTo(parentDiv) +} diff --git a/app/templates/stats/graph.html b/app/templates/stats/graph.html index ad7150f..53da789 100644 --- a/app/templates/stats/graph.html +++ b/app/templates/stats/graph.html @@ -8,8 +8,14 @@ + {% endblock %} {% block content %}
+ +
+

Filter

+
+
{% endblock %} \ No newline at end of file