Refactor History plot

This commit is contained in:
Edgar P. Burkhart 2024-01-02 10:58:30 +01:00
parent 6b50de5e35
commit 0940904cd8
Signed by: edpibu
GPG Key ID: 9833D3C5A25BD227
4 changed files with 130 additions and 105 deletions

View File

@ -18,57 +18,23 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for y in history.years reversed %} {% for date in history.data reversed %}
{% for date in y.d reversed %} {% ifchanged %}
{% ifchanged %} {% if date.sum_m or date.sum_p %}
{% if date %} <tr {% if not date.month.month|divisibleby:"2" %}class="even"{% endif %}>
<tr {% if not date.month.month|divisibleby:"2" %}class="even"{% endif %}> <td class="icon">{% up_down_icon date.sum %}</td>
<td class="icon"> <th class="date" scope="row">{% month_url date.month account=account category=category %}</th>
<span class="ri-{% if date.sum > 0 %}arrow-up-s-line green{% elif date.sum < 0 %}arrow-down-s-line red{% endif %}"></span> <td class="value">{{ date.sum_m|pmrvalue }}</td>
</td> <td class="bar m">{% plot_bar date.sum date.sum_m history.max.pm %}</td>
<th class="date" scope="row"> <td class="bar p">{% plot_bar date.sum date.sum_p history.max.pm %}</td>
{% if date.has_transactions %} <td class="value">{{ date.sum_p|pmrvalue }}</td>
{% if account %} </tr>
<a href="{% url "account_transaction_month" account=account.pk year=date.month.year month=date.month.month %}">{{ date.month|date:"Y-m" }}</a> {% else %}
{% elif category %} <tr class="empty">
<a href="{% url "category_transaction_month" category=category.pk year=date.month.year month=date.month.month %}">{{ date.month|date:"Y-m" }}</a> <td colspan="6" class="empty"></td>
{% else %} </tr>
<a href="{% url "transaction_month" year=date.month.year month=date.month.month %}">{{ date.month|date:"Y-m" }}</a> {% endif %}
{% endif %} {% endifchanged %}
{% else %}
{{ date.month|date:"Y-m" }}
{% endif %}
</th>
<td class="value">{{ date.sum_m|pmrvalue }}</td>
<td class="bar m">
{% if date.sum_m %}
<div style="width: {% widthratio date.sum_m history.max -100 %}%"></div>
{% endif %}
{% if date.sum < 0 %}
<div class="tot" style="width:{% widthratio date.sum history.max -100 %}%">
<span>{{ date.sum|pmrvalue }}</span>
</div>
{% endif %}
</td>
<td class="bar p">
{% if date.sum_p %}
<div style="width: {% widthratio date.sum_p history.max 100 %}%"></div>
{% endif %}
{% if date.sum > 0 %}
<div class="tot" style="width:{% widthratio date.sum history.max 100 %}%">
<span>{{ date.sum|pmrvalue }}</span>
</div>
{% endif %}
</td>
<td class="value">{{ date.sum_p|pmrvalue }}</td>
</tr>
{% else %}
<tr class="empty">
<td colspan="6" class="empty"></td>
</tr>
{% endif %}
{% endifchanged %}
{% endfor %}
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
@ -78,36 +44,26 @@
<thead> <thead>
<tr> <tr>
<th scope="col">{% translate "Year" %}</th> <th scope="col">{% translate "Year" %}</th>
<th scope="col">01</th> {% calendar_head %}
<th scope="col">02</th>
<th scope="col">03</th>
<th scope="col">04</th>
<th scope="col">05</th>
<th scope="col">06</th>
<th scope="col">07</th>
<th scope="col">08</th>
<th scope="col">09</th>
<th scope="col">10</th>
<th scope="col">11</th>
<th scope="col">12</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for year in history.years reversed %} {% regroup history.data by month.year as years_list %}
{% for y, year in years_list reversed %}
<tr> <tr>
<th>{{ year.y }}</th> <th>{{ y }}</th>
{% for m in year.d %} {% for m in year %}
{% if forloop.parentloop.last and forloop.first %} {% if forloop.parentloop.last and forloop.first %}
{% for _ in history.offset.0 %}<td></td>{% endfor %} {% empty_calendar_cells_start m.month.month %}
{% endif %} {% endif %}
{% if m %} {% if m %}
<td class="{% if m.sum > 0 %}p{% else %}m{% endif %}" <td class="{% if m.sum > 0 %}p{% else %}m{% endif %}"
style="opacity: {% opacity m.sum history.years_max %}"></td> style="opacity: {% opacity m.sum history.max.sum %}"></td>
{% else %} {% else %}
<td></td> <td></td>
{% endif %} {% endif %}
{% if forloop.parentloop.first and forloop.last %} {% if forloop.parentloop.first and forloop.last %}
{% for _ in history.offset.1 %}<td></td>{% endfor %} {% empty_calendar_cells_end m.month.month %}
{% endif %} {% endif %}
{% endfor %} {% endfor %}
</tr> </tr>

View File

@ -1,6 +1,6 @@
import datetime import datetime
from django.db.models import Func, Max, Min, Q, Sum, Value from django.db.models import Func, Max, Min, Q, Sum
from django.db.models.functions import Abs, TruncMonth from django.db.models.functions import Abs, TruncMonth
@ -23,34 +23,30 @@ def history(transaction_set):
sum_p=Sum("value", filter=Q(value__gt=0)), sum_p=Sum("value", filter=Q(value__gt=0)),
sum_m=Sum("value", filter=Q(value__lt=0)), sum_m=Sum("value", filter=Q(value__lt=0)),
sum=Sum("value"), sum=Sum("value"),
has_transactions=Value(1),
).order_by("-month") ).order_by("-month")
_data = [ _data = [
{ _history.filter(month=datetime.date(y, m + 1, 1)).first()
"y": y, or {"month": datetime.date(y, m + 1, 1), "sum": 0}
"d": [
_history.filter(month=datetime.date(y, m + 1, 1)).first()
for m in range(
_first_month.month - 1 if _first_month.year == y else 0,
_last_month.month if _last_month.year == y else 12,
)
],
}
for y in range( for y in range(
_first_month.year, _first_month.year,
_last_month.year + 1, _last_month.year + 1,
) )
for m in range(
_first_month.month - 1 if _first_month.year == y else 0,
_last_month.month if _last_month.year == y else 12,
)
] ]
return { return {
"years": _data, "data": _data,
"offset": [range(_first_month.month - 1), range(12 - _last_month.month)], "max": {
"max": max( "pm": max(
_history.aggregate( _history.aggregate(
max=Max("sum_p", default=0), max=Max("sum_p", default=0),
min=-Min("sum_m", default=0), min=-Min("sum_m", default=0),
).values(), ).values(),
), ),
"years_max": _history.aggregate(max=Max(Abs("sum")))["max"], "sum": _history.aggregate(max=Max(Abs("sum")))["max"],
},
} }

View File

@ -65,12 +65,17 @@ table.full-width col.bar {
} }
} }
} }
tr.empty {
height: 0.5rem; tbody tr {
} background: initial;
tr.even { &.empty {
background: #eeeeff; height: 0.5rem;
}
&.even {
background: #eeeeff;
}
} }
tfoot { tfoot {
background: var(--bg-01); background: var(--bg-01);
} }
@ -78,22 +83,23 @@ table.full-width col.bar {
.calendar { .calendar {
margin-top: var(--gap); margin-top: var(--gap);
font-feature-settings: var(--num);
tbody tr {
background: initial;
}
.p { .p {
background: var(--green); background: var(--green);
} }
.m { .m {
background: var(--red); background: var(--red);
} }
tbody tr { table {
&:not(:last-child) { tbody tr {
border-bottom: none; background: initial;
} &:not(:last-child) {
&:not(:first-child) { border-bottom: none;
border-top: none; }
&:not(:first-child) {
border-top: none;
}
} }
} }
} }

View File

@ -1,7 +1,10 @@
import datetime
import math import math
from django import template from django import template
from django.template.defaultfilters import date
from django.templatetags.static import static from django.templatetags.static import static
from django.urls import reverse
from django.utils import formats from django.utils import formats
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
@ -73,4 +76,68 @@ def css(href):
@register.simple_tag @register.simple_tag
def opacity(v, vmax): def opacity(v, vmax):
return f"{math.sin(math.fabs(v/vmax)*math.pi/2):.2f}" return f"{math.sin(math.fabs(v/vmax)*math.pi/2):.3f}"
@register.simple_tag
def empty_calendar_cells(n):
return mark_safe(n * "<td></td>")
@register.simple_tag
def empty_calendar_cells_start(n):
return empty_calendar_cells(n - 1)
@register.simple_tag
def empty_calendar_cells_end(n):
return empty_calendar_cells(12 - n)
@register.simple_tag
def up_down_icon(val):
if val > 0:
return mark_safe("""<span class="ri-arrow-up-s-line green"></span>""")
elif val < 0:
return mark_safe("""<span class="ri-arrow-down-s-line red"></span>""")
@register.simple_tag
def month_url(month, account=None, category=None):
url_name = "transaction_month"
url_params = {"year": month.year, "month": month.month}
if account:
url_name = "account_" + url_name
url_params |= {"account": account.pk}
elif category:
url_name = "category_" + url_name
url_params |= {"category": category.pk}
url = reverse(url_name, kwargs=url_params)
return mark_safe(f"""<a href="{url}">{ date(month, "Y-m") }</a>""")
@register.simple_tag
def plot_bar(s, sum_pm, s_max):
_res = ""
if sum_pm:
_w = abs(sum_pm / s_max)
_res += f"""<div style="width: {_w:.1%}"></div>"""
if sum_pm is not None and s * sum_pm > 0:
_w = abs(s / s_max)
_res += (
f"""<div class="tot" style="width: {_w:.1%}">"""
f"""<span>{pmrvalue(s)}</span></div>"""
)
return mark_safe(_res)
@register.simple_tag
def calendar_head():
months = (datetime.date(1, m + 1, 1) for m in range(12))
th = (f"""<th>{date(month, "m")}</th>""" for month in months)
return mark_safe("".join(th))