add graph of total exp per day for last 7 days

This commit is contained in:
Wojciech Kwolek 2020-09-14 00:27:42 +02:00
parent 64037e5a7a
commit ad2f8d9093
5 changed files with 128 additions and 0 deletions

View File

@ -28,4 +28,7 @@ def create_app():
from . import stats
app.register_blueprint(stats.blueprint)
from . import graph
app.register_blueprint(graph.blueprint)
return app

View File

@ -1,3 +1,4 @@
from sqlalchemy.sql import func
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_login import UserMixin
@ -63,6 +64,11 @@ class Accomplishment(db.Model):
return Accomplishment.query.filter(
Accomplishment.time >= start, Accomplishment.time < end, Accomplishment.user_id == user_id).all()
def get_time_range_total(user_id, start, end):
result = db.session.query(func.sum(Accomplishment.difficulty).label('total')).filter(
Accomplishment.time >= start, Accomplishment.time < end, Accomplishment.user_id == user_id)[0][0]
return result if result is not None else 0
@staticmethod
def get_day(user_id, day):
# TODO: allow setting custom "start of day" hour
@ -70,7 +76,17 @@ class Accomplishment(db.Model):
end = timeutils.day_after(day)
return Accomplishment.get_time_range(user_id, start, end)
def get_day_total(user_id, day):
start = timeutils.day(day)
end = timeutils.day_after(day)
return Accomplishment.get_time_range_total(user_id, start, end)
@staticmethod
def get_today(user_id):
today = datetime.now()
return Accomplishment.get_day(user_id, today)
@staticmethod
def get_today_total(user_id):
today = datetime.now()
return Accomplishment.get_day_total(user_id, today)

69
app/graph.py Normal file
View File

@ -0,0 +1,69 @@
from flask import Blueprint, render_template
from flask_login import login_required, current_user
from .db import db, Accomplishment
from . import timeutils
blueprint = Blueprint('graph', __name__)
@blueprint.route('/graph.svg')
@login_required
def graph_svg():
count = 7
accomplishments = [0]*count
days = [""]*count
day = timeutils.today()
for i in range(1, count+1):
total_xp = Accomplishment.get_day_total(current_user.id, day)
accomplishments[-i] = total_xp
days[-i] = day.strftime('%a')[:2]
day = timeutils.day_before(day)
print(accomplishments)
return render_template('graph.svg', days=days, **gen_graph_data(accomplishments)), 200, {'Content-Type': 'image/svg+xml'}
def gen_scale(base=10):
return [base*i for i in range(0, 5)]
def find_scale_base(max_n):
if max_n < 20:
return 10
return (max_n - (max_n - 1) % 20 + 20) // 4
n = max_n % 20
while n % 20 != 0:
n += 1
return n//4
GRAPH_TOP_LINE = 16.6
GRAPH_BOTTOM_LINE = 83
GRAPH_RANGE = GRAPH_BOTTOM_LINE - GRAPH_TOP_LINE
def absolute_to_percentage_position(n, scale_base):
scale_top = scale_base * 4
return round(GRAPH_BOTTOM_LINE - n/scale_top * GRAPH_RANGE, 2)
def gen_graph_data(numbers):
assert len(numbers) > 1
max_n = max(numbers)
scale_base = find_scale_base(max_n)
scale = gen_scale(scale_base)
dots = [absolute_to_percentage_position(
n, scale_base) for n in numbers]
lines = list(zip(dots, dots[1:]))
return {
"dots": dots,
"lines": lines,
"scale": scale,
}

37
app/templates/graph.svg Normal file
View File

@ -0,0 +1,37 @@
{% macro x(n) -%}
{{ 15+((85-15)/(lines|length))*(n-1) }}%
{%- endmacro %}
<svg version="1.1"
baseProfile="full"
xmlns="http://www.w3.org/2000/svg">
<style>
svg {
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; /* 1 */
}
</style>
<g class="bg">
{% for n in scale %}
<line x1="10%" y1="{{ 16.6 * loop.index }}%" x2="90%" y2="{{ 16.6 * loop.index }}%" stroke="#ddd" stroke-width="2px" />
{% endfor %}
</g>
<g class="legend" style="font-size: 0.9em; text-align:right;">
{% for n in scale | reverse %}
<text x="10%" y="{{ 16.6 * (loop.index) }}%" style="text-anchor: end; font-weight: semibold" dx="-.6em" dy=".3em" fill="#aaa">{{ n }}</text>
{% endfor %}
</g>
<g class="lines">
{% for y1, y2 in lines %}
<line x1="{{ x(loop.index) }}" y1="{{ y1 }}%" x2="{{ x(loop.index + 1) }}" y2="{{ y2 }}%" stroke="#48bb78" stroke-width="3" />
{% endfor %}
</g>
<g class="dots">
{% for cy in dots %}
<circle cx="{{ x(loop.index) }}" cy="{{ cy }}%" r="4" stroke-width="0" fill="#2f855a" />
{% endfor %}
</g>
<g class="legend" style="font-size: 0.9em; text-anchor: middle">
{% for day in days %}
<text x="{{ x(loop.index) }}" y="95%" fill="#888">{{ day }}</text>
{% endfor %}
</g>
</svg>

View File

@ -12,6 +12,9 @@
</div>
</form>
</div>
<div class="max-w-lg mx-auto card">
<img class="w-full h-48" src="{{ url_for('graph.graph_svg') }}">
</div>
<div class="max-w-lg mx-auto card">
<div class="flex items-center justify-between mb-4">
<div>