From dae0e233b8674f095bbcd4c55d1a662ae63e3f26 Mon Sep 17 00:00:00 2001 From: Fabian Dill Date: Fri, 8 Apr 2022 15:52:49 +0200 Subject: [PATCH] WebHost: add a /stats page --- WebHostLib/__init__.py | 3 +- WebHostLib/requirements.txt | 1 + WebHostLib/stats.py | 70 +++++++++++++++++++++++++++++++ WebHostLib/templates/siteMap.html | 1 + WebHostLib/templates/stats.html | 21 ++++++++++ 5 files changed, 95 insertions(+), 1 deletion(-) create mode 100644 WebHostLib/stats.py create mode 100644 WebHostLib/templates/stats.html diff --git a/WebHostLib/__init__.py b/WebHostLib/__init__.py index b13a0961..d818ffa1 100644 --- a/WebHostLib/__init__.py +++ b/WebHostLib/__init__.py @@ -207,6 +207,7 @@ def get_datapackge(): return Response(json.dumps(network_data_package, indent=4), mimetype="text/plain") +@app.route('/index') @app.route('/sitemap') def get_sitemap(): available_games = [] @@ -217,6 +218,6 @@ def get_sitemap(): from WebHostLib.customserver import run_server_process -from . import tracker, upload, landing, check, generate, downloads, api # to trigger app routing picking up on it +from . import tracker, upload, landing, check, generate, downloads, api, stats # to trigger app routing picking up on it app.register_blueprint(api.api_endpoints) diff --git a/WebHostLib/requirements.txt b/WebHostLib/requirements.txt index 40464091..da8f682a 100644 --- a/WebHostLib/requirements.txt +++ b/WebHostLib/requirements.txt @@ -4,3 +4,4 @@ waitress>=2.1.0 flask-caching>=1.10.1 Flask-Compress>=1.11 Flask-Limiter>=2.2.0 +bokeh>=2.4.2 \ No newline at end of file diff --git a/WebHostLib/stats.py b/WebHostLib/stats.py new file mode 100644 index 00000000..62c5dea2 --- /dev/null +++ b/WebHostLib/stats.py @@ -0,0 +1,70 @@ +from collections import Counter, defaultdict +from itertools import cycle +from datetime import datetime, timedelta, date +from math import pi + +from bokeh.embed import components +from bokeh.palettes import Dark2_8 as palette +from bokeh.plotting import figure, ColumnDataSource +from bokeh.resources import INLINE +from flask import render_template +from pony.orm import select + +from . import app, cache +from .models import Room + +pi2 = 2 * pi + + +@app.route('/stats') +@cache.memoize(timeout=60*60) # regen once per hour should be plenty +def stats(): + plot = figure(title="Games played per day", x_axis_type='datetime', x_axis_label="Date", y_axis_label="Played", + sizing_mode="scale_both") + games_played = defaultdict(Counter) + total_games = Counter() + room: Room + games = set() + cutoff = date.today()-timedelta(days=30000) + for room in select(room for room in Room if room.creation_time >= cutoff): + for slot in room.seed.slots: + total_games[slot.game] += 1 + games.add(slot.game) + games_played[room.creation_time.date()][slot.game] += 1 + days = sorted(games_played) + cyc_palette = cycle(palette) + for game in sorted(games): + occurences = [] + for day in days: + occurences.append(games_played[day][game]) + plot.line([datetime.combine(day, datetime.min.time()) for day in days], + occurences, legend_label=game, line_width=2, color=next(cyc_palette)) + total = sum(total_games.values()) + pie = figure(plot_height=350, title=f"Games played in the last 30 days. (Total: {total})", toolbar_location=None, + tools="hover", tooltips=[("Game:", "@games"), ("Played:", "@count")], + sizing_mode="scale_both") + pie.axis.visible = False + + data = { + "games": [], + "count": [], + "start_angles": [], + "end_angles": [], + } + current_angle = 0 + for i, (game, count) in enumerate(total_games.most_common()): + data["games"].append(game) + data["count"].append(count) + data["start_angles"].append(current_angle) + angle = count / total * pi2 + current_angle += angle + data["end_angles"].append(current_angle) + + data["colors"] = [element[1] for element in sorted((game, color) for game, color in zip(data["games"], cycle(palette)))] + pie.wedge(x=0.5, y=0.5, radius=0.5, + start_angle="start_angles", end_angle="end_angles", fill_color="colors", + source=ColumnDataSource(data=data), legend_field="games") + + script, charts = components((plot, pie)) + return render_template("stats.html", js_resources=INLINE.render_js(), css_resources=INLINE.render_css(), + chart_data=script, charts=charts) diff --git a/WebHostLib/templates/siteMap.html b/WebHostLib/templates/siteMap.html index dcce7727..9626aef3 100644 --- a/WebHostLib/templates/siteMap.html +++ b/WebHostLib/templates/siteMap.html @@ -25,6 +25,7 @@
  • Tutorials Page
  • User Content
  • Weighted Settings Page
  • +
  • Game Statistics
  • Game Info Pages

    diff --git a/WebHostLib/templates/stats.html b/WebHostLib/templates/stats.html new file mode 100644 index 00000000..39594076 --- /dev/null +++ b/WebHostLib/templates/stats.html @@ -0,0 +1,21 @@ +{% extends 'pageWrapper.html' %} + +{% block head %} + Site Map + + {{ css_resources|indent(4)|safe }} + {{ js_resources|indent(4)|safe }} + {{ chart_data|indent(4)|safe }} +{% endblock %} + +{% block body %} + {% include 'header/grassFlowersHeader.html' %} +
    +

    Stats

    + {% for chart in charts %} + {{ chart|indent(12)|safe }} + {% endfor %} + + +
    +{% endblock %}