diff --git a/cmd/clitest/main.go b/cmd/clitest/main.go index e3f5f72..24c3b01 100644 --- a/cmd/clitest/main.go +++ b/cmd/clitest/main.go @@ -64,6 +64,21 @@ func main() { like = append(like, ledger.LikeAfter(*likeAfter)) foo := func(w http.ResponseWriter, r *http.Request) { + switch r.URL.Path { + case "/bal", "/reg": + case "/ui": + f, err := os.Open("./index.html") + if err != nil { + panic(err) + } + defer f.Close() + io.Copy(w, f) + return + default: + http.NotFound(w, r) + return + } + foolike := make(ledger.Likes, 0) for _, v := range r.URL.Query()["likeName"] { foolike = append(foolike, ledger.LikeName(v)) @@ -79,6 +94,7 @@ func main() { } register := deltas.Like(foolike...).Register() + predicted := make(ledger.Register) bpis := maps.Clone(bpis) // MODIFIERS @@ -103,8 +119,8 @@ func main() { panic(k) } } - predicted := prediction.Predict(register, window) - register.PushAll(predicted) // TODO draw line separately + predicted = prediction.Predict(register, window) + for _, currencyRate := range r.URL.Query()["predictFixedGrowth"] { currency := strings.Split(currencyRate, "=")[0] rate, err := strconv.ParseFloat(strings.Split(currencyRate, "=")[1], 64) @@ -117,94 +133,92 @@ func main() { } } } + if r.URL.Query().Get("bpi") != "" { register = register.WithBPIs(bpis) + predicted = predicted.WithBPIs(bpis) } if zoomStart, err := time.ParseInLocation("2006-01", r.URL.Query().Get("zoomStart"), time.Local); err == nil { register = register.Between(zoomStart, time.Now().Add(time.Hour*24*365*100)) + predicted = predicted.Between(zoomStart, time.Now().Add(time.Hour*24*365*100)) } // /MODIFIERS - nameCurrencyDateValue := map[string]map[ledger.Currency]map[string]float64{} - for date, balances := range register { - for name, balance := range balances { - for currency, value := range balance { - if _, ok := nameCurrencyDateValue[name]; !ok { - nameCurrencyDateValue[name] = make(map[ledger.Currency]map[string]float64) - } - if _, ok := nameCurrencyDateValue[name][currency]; !ok { - nameCurrencyDateValue[name][currency] = make(map[string]float64) - } - nameCurrencyDateValue[name][currency][date] += value - } - } - } - - chart := NewChart("line") - if v := r.URL.Query().Get("chart"); v != "" { - chart = NewChart(v) - } - - dates := register.Dates() - names := register.Names() - chart.AddX(dates) - - switch r.URL.Path { - default: - http.NotFound(w, r) - return - case "/ui": - f, err := os.Open("./index.html") - if err != nil { - panic(err) - } - defer f.Close() - io.Copy(w, f) - return - case "/bal": - for _, name := range names { - currencyDateValue := nameCurrencyDateValue[name] - for currency, dateValue := range currencyDateValue { - series := make([]int, len(dates)) - for i := range dates { - var lastValue float64 - for j := range dates[:i+1] { - if newLastValue, ok := dateValue[dates[j]]; ok { - lastValue = newLastValue - } + toChart := func(cumulative bool, display string, register ledger.Register) Chart { + nameCurrencyDateValue := map[string]map[ledger.Currency]map[string]float64{} + for date, balances := range register { + for name, balance := range balances { + for currency, value := range balance { + if _, ok := nameCurrencyDateValue[name]; !ok { + nameCurrencyDateValue[name] = make(map[ledger.Currency]map[string]float64) } - series[i] = int(lastValue) - } - key := fmt.Sprintf("%s (%s)", name, currency) - if slices.Min(series) != 0 || slices.Max(series) != 0 { - chart.AddY(key, series) - } - } - } - case "/reg": - for _, name := range names { - currencyDateValue := nameCurrencyDateValue[name] - for currency, dateValue := range currencyDateValue { - series := make([]int, len(dates)) - for i := range dates { - var prevValue float64 - var lastValue float64 - for j := range dates[:i+1] { - if newLastValue, ok := dateValue[dates[j]]; ok { - prevValue = lastValue - lastValue = newLastValue - } + if _, ok := nameCurrencyDateValue[name][currency]; !ok { + nameCurrencyDateValue[name][currency] = make(map[string]float64) } - series[i] = int(lastValue - prevValue) - } - key := fmt.Sprintf("%s (%s)", name, currency) - if slices.Min(series) != 0 || slices.Max(series) != 0 { - chart.AddY(key, series) + nameCurrencyDateValue[name][currency][date] += value } } } + + chart := NewChart("line") + if v := display; v != "" { + chart = NewChart(v) + } + + dates := register.Dates() + names := register.Names() + chart.AddX(dates) + + if cumulative { + for _, name := range names { + currencyDateValue := nameCurrencyDateValue[name] + for currency, dateValue := range currencyDateValue { + series := make([]int, len(dates)) + for i := range dates { + var lastValue float64 + for j := range dates[:i+1] { + if newLastValue, ok := dateValue[dates[j]]; ok { + lastValue = newLastValue + } + } + series[i] = int(lastValue) + } + key := fmt.Sprintf("%s (%s)", name, currency) + if slices.Min(series) != 0 || slices.Max(series) != 0 { + chart.AddY(key, series) + } + } + } + } else { + for _, name := range names { + currencyDateValue := nameCurrencyDateValue[name] + for currency, dateValue := range currencyDateValue { + series := make([]int, len(dates)) + for i := range dates { + var prevValue float64 + var lastValue float64 + for j := range dates[:i+1] { + if newLastValue, ok := dateValue[dates[j]]; ok { + prevValue = lastValue + lastValue = newLastValue + } + } + series[i] = int(lastValue - prevValue) + } + key := fmt.Sprintf("%s (%s)", name, currency) + if slices.Min(series) != 0 || slices.Max(series) != 0 { + chart.AddY(key, series) + } + } + } + } + return chart } - if err := chart.Render(w); err != nil { + primary := toChart(r.URL.Path == "/bal", r.URL.Query().Get("chart"), register) + if len(predicted) > 0 { + primary.Overlap(toChart(r.URL.Path == "/bal", "line", predicted)) + } + if err := primary.Render(w); err != nil { panic(err) } } @@ -277,6 +291,7 @@ type Chart interface { AddX(interface{}) AddY(string, []int) Render(io.Writer) error + Overlap(Chart) } func NewChart(name string) Chart { @@ -312,6 +327,14 @@ func (line Line) AddY(name string, v []int) { line.AddSeries(name, y) } +func (line Line) Overlap(other Chart) { + overlapper, ok := other.(charts.Overlaper) + if !ok { + panic(fmt.Sprintf("cannot overlap %T", other)) + } + line.Line.Overlap(overlapper) +} + type Bar struct { *charts.Bar } @@ -332,6 +355,14 @@ func (bar Bar) AddY(name string, v []int) { bar.AddSeries(name, y) } +func (bar Bar) Overlap(other Chart) { + overlapper, ok := other.(charts.Overlaper) + if !ok { + panic(fmt.Sprintf("cannot overlap %T", other)) + } + bar.Bar.Overlap(overlapper) +} + type Stack struct { Bar }