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") +}