diff --git a/cmd/http/public/transactions.html b/cmd/http/public/transactions.html
index 896b201..fdbdc8a 100644
--- a/cmd/http/public/transactions.html
+++ b/cmd/http/public/transactions.html
@@ -205,6 +205,10 @@
Look at this graph
+
+ Where did the money go
+
+
Edit
diff --git a/cmd/http/router.go b/cmd/http/router.go
index 60c4c6c..26afd04 100644
--- a/cmd/http/router.go
+++ b/cmd/http/router.go
@@ -74,6 +74,8 @@ func (router Router) API(w http.ResponseWriter, r *http.Request) {
router.APITransactions(w, r)
case "/api/amend":
router.APIAmend(w, r)
+ case "/api/trends":
+ router.APITrends(w, r)
case "/api/reg", "/api/bal":
router.APIReg(w, r)
default:
@@ -110,6 +112,51 @@ func (router Router) APITransactions(w http.ResponseWriter, r *http.Request) {
})
}
+func (router Router) APITrends(w http.ResponseWriter, r *http.Request) {
+ bpis, err := router.bpis()
+ if err != nil {
+ panic(err)
+ }
+
+ deltas, err := router.files.Deltas()
+ if err != nil {
+ panic(err)
+ }
+
+ recent := time.Hour * 24 * 365 / 2
+ recentHouseRelatedDeltas := deltas.
+ Like(ledger.LikeTransactions(
+ deltas.Like(ledger.LikeName(`^House`))...,
+ )).
+ Like(ledger.LikeAfter(time.Now().Add(-1 * recent).Format("2006-01"))).
+ Group(ledger.GroupName(`Withdrawal:[0-9]*:[^:]*`)).
+ Group(ledger.GroupDate(`^[0-9]*-[0-9]*`)).
+ Like(ledger.LikeNotName(`^$`))
+
+ monthsToDeltas := map[string]ledger.Deltas{}
+ for _, delta := range recentHouseRelatedDeltas {
+ monthsToDeltas[delta.Date] = append(monthsToDeltas[delta.Date], delta)
+ }
+ months := []string{}
+ for k := range monthsToDeltas {
+ months = append(months, k)
+ }
+ slices.Sort(months)
+
+ fmt.Fprintln(w, "")
+ for _, month := range months {
+ balances := monthsToDeltas[month].Balances().WithBPIs(bpis)
+ chart := view.NewChart("pie")
+ for category, balance := range balances {
+ chart.AddY(category, []int{int(balance[ledger.USD])})
+ }
+ fmt.Fprintln(w, "", month, "
")
+ if err := chart.Render(w); err != nil {
+ panic(err)
+ }
+ }
+}
+
func (router Router) APIAmend(w http.ResponseWriter, r *http.Request) {
b, _ := io.ReadAll(r.Body)
diff --git a/src/ledger/like.go b/src/ledger/like.go
index cba6b6a..8c28e7b 100644
--- a/src/ledger/like.go
+++ b/src/ledger/like.go
@@ -35,6 +35,12 @@ func LikeAfter(date string) Like {
}
}
+func LikeNotName(pattern string) Like {
+ return func(d Delta) bool {
+ return !like(pattern, d.Name)
+ }
+}
+
func LikeName(pattern string) Like {
return func(d Delta) bool {
return like(pattern, d.Name)
diff --git a/src/view/chart.go b/src/view/chart.go
index 3778a71..7495a5e 100644
--- a/src/view/chart.go
+++ b/src/view/chart.go
@@ -23,6 +23,8 @@ func NewChart(name string) Chart {
return NewBar()
case "stack":
return NewStack()
+ case "pie":
+ return NewPie()
default:
panic("bad chart name " + name)
}
@@ -107,3 +109,33 @@ func (stack Stack) AddY(name string, v []int) {
Stack: "stackA",
}))
}
+
+type Pie struct {
+ *charts.Pie
+ series []opts.PieData
+}
+
+func NewPie() *Pie {
+ return &Pie{Pie: charts.NewPie()}
+}
+
+func (pie *Pie) AddX(v interface{}) {
+}
+
+func (pie *Pie) Render(w io.Writer) error {
+ pie.AddSeries("", pie.series)
+ return pie.Pie.Render(w)
+}
+
+func (pie *Pie) AddY(name string, v []int) {
+ for _, v := range v {
+ pie.series = append(pie.series, opts.PieData{
+ Name: fmt.Sprintf("%s ($%d)", name, v),
+ Value: v,
+ })
+ }
+}
+
+func (pie *Pie) Overlap(other Chart) {
+ panic("nope")
+}