Compare commits

...

2 commits

Author SHA1 Message Date
19490e62b2
Add category plot 2022-12-19 14:36:02 +01:00
e095d8d35f
Add first plot 2022-12-19 14:18:25 +01:00
11 changed files with 121 additions and 1 deletions

View file

@ -194,7 +194,7 @@ class Snapshot(models.Model):
@property @property
def sum(self): def sum(self):
if self.previous is None: if self.previous is None:
return None return 0
trans = self.transactions.aggregate(sum=models.Sum("value")) trans = self.transactions.aggregate(sum=models.Sum("value"))
return trans["sum"] or 0 return trans["sum"] or 0
@ -206,6 +206,24 @@ class Snapshot(models.Model):
date__lte=self.date, date__gt=self.previous.date date__lte=self.date, date__gt=self.previous.date
) )
@property
def pos(self):
return (
self.transactions.filter(value__gt=0).aggregate(sum=models.Sum("value"))[
"sum"
]
or 0
)
@property
def neg(self):
return (
self.transactions.filter(value__lt=0).aggregate(sum=models.Sum("value"))[
"sum"
]
or 0
)
class Meta: class Meta:
ordering = ["-date"] ordering = ["-date"]
verbose_name = _("Snapshot") verbose_name = _("Snapshot")

View file

@ -20,6 +20,7 @@ from django.views.generic.base import RedirectView
urlpatterns = i18n_patterns( urlpatterns = i18n_patterns(
path("", include("main.urls")), path("", include("main.urls")),
path("plot/", include("plot.urls")),
path("admin/", admin.site.urls), path("admin/", admin.site.urls),
prefix_default_language=False, prefix_default_language=False,
) )

0
nummi/plot/__init__.py Normal file
View file

3
nummi/plot/admin.py Normal file
View file

@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

6
nummi/plot/apps.py Normal file
View file

@ -0,0 +1,6 @@
from django.apps import AppConfig
class PlotConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "plot"

View file

3
nummi/plot/models.py Normal file
View file

@ -0,0 +1,3 @@
from django.db import models
# Create your models here.

13
nummi/plot/nummi.mplstyle Normal file
View file

@ -0,0 +1,13 @@
font.family: Inter
lines.linewidth: 2
figure.autolayout: True
figure.figsize: 8, 4
figure.dpi: 300
axes.prop_cycle: cycler('color', ["66cc66", "#338033", "#99ff99", "#802653", "#cc6699"])
axes.axisbelow: True
axes.grid: True
svg.fonttype: none

3
nummi/plot/tests.py Normal file
View file

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

8
nummi/plot/urls.py Normal file
View file

@ -0,0 +1,8 @@
from django.urls import path
from . import views
urlpatterns = [
path("timeline", views.timeline, name="timeline"),
path("categories", views.categories, name="categories"),
]

65
nummi/plot/views.py Normal file
View file

@ -0,0 +1,65 @@
import io
import matplotlib
import matplotlib.pyplot as plt
from matplotlib import dates as mdates
from django.db import models
from django.contrib.auth.decorators import login_required
from django.http import HttpResponse
from django.utils.translation import gettext as _
from main.models import (
Transaction,
Invoice,
Category,
Snapshot,
)
matplotlib.use("Agg")
plt.style.use("./plot/nummi.mplstyle")
@login_required
def timeline(request):
_snapshots = Snapshot.objects.all()
fig, ax = plt.subplots()
ax.step(
[s.date for s in _snapshots],
[s.value for s in _snapshots],
where="post",
)
ax.set(ylabel=_("Snapshots"), ylim=0)
ax.xaxis.set_major_formatter(
mdates.ConciseDateFormatter(ax.xaxis.get_major_locator())
)
ax.autoscale(True, "x", True)
_io = io.StringIO()
fig.savefig(_io, format="svg")
return HttpResponse(_io.getvalue(), headers={"Content-Type": "image/svg+xml"})
@login_required
def categories(request):
_categories = Category.objects.all()
fig, ax = plt.subplots(figsize=(8, _categories.count() / 4))
ax.barh(
[str(c) for c in _categories][::-1],
[
Transaction.objects.filter(category=c).aggregate(sum=models.Sum("value"))[
"sum"
]
for c in _categories
][::-1],
)
_io = io.StringIO()
fig.savefig(_io, format="svg")
return HttpResponse(_io.getvalue(), headers={"Content-Type": "image/svg+xml"})