From b1c5b729d17905cfaef32b57edaa7b51a35317af Mon Sep 17 00:00:00 2001 From: gutmet Date: Sun, 14 Jun 2020 11:06:20 +0200 Subject: [PATCH] fix trend calculation if days are missing by linear interpolation --- hdiet.go | 76 ++++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 60 insertions(+), 16 deletions(-) diff --git a/hdiet.go b/hdiet.go index d7261aa..7dc3560 100644 --- a/hdiet.go +++ b/hdiet.go @@ -102,14 +102,21 @@ type yRange struct { max float64 } -func process(file string) ([]point, error) { +func readFile(file string) (map[time.Time]float64, time.Time, time.Time) { + optPanic := func(err error) { + if err != nil { + panic("process:" + err.Error()) + } + } weights, err := goutil.ReadFile(file) if err != nil { - return nil, err + return nil, time.Time{}, time.Time{} } lines := strings.Split(weights, "\n") - trend := 0.0 - s := []point{} + var firstDay time.Time + firstDayIsSet := false + var lastDay time.Time + dateToWeight := make(map[time.Time]float64) for _, line := range lines { line = strings.TrimSpace(line) if len(line) == 0 { @@ -117,18 +124,58 @@ func process(file string) ([]point, error) { } splitline := strings.Split(line, "\t") if len(splitline) < 2 { - return nil, errors.New("process - invalid line in weight file: " + line) + optPanic(errors.New("invalid line in weight file " + file + ": " + line)) + } + date, err := time.Parse(dateLayout, splitline[0]) + optPanic(err) + weight, err := strconv.ParseFloat(splitline[1], 64) + optPanic(err) + if !firstDayIsSet { + firstDay = date + firstDayIsSet = true + } + lastDay = date + dateToWeight[date] = weight + } + return dateToWeight, firstDay, lastDay +} + +func process(file string) []point { + dateToWeight, firstDay, lastDay := readFile(file) + if dateToWeight == nil { + return []point{} + } + trend := 0.0 + points := []point{} + for d := firstDay; d.Before(lastDay) || d == lastDay; d = d.AddDate(0, 0, 1) { + var weight float64 + if w, ok := dateToWeight[d]; ok { + weight = w + } else { + prevPoint := points[len(points)-1] + prevDay := prevPoint.date + prevWeight := prevPoint.weight + var nextWeight float64 + var nextDay time.Time + for nextDay = d.AddDate(0, 0, 1); nextDay.Before(lastDay) || nextDay == lastDay; nextDay = nextDay.AddDate(0, 0, 1) { + if w, ok := dateToWeight[nextDay]; ok { + nextWeight = w + break + } + } + days := nextDay.Sub(prevDay).Hours() / 24 + changePerDay := (nextWeight - prevWeight) / days + weight = prevWeight + changePerDay } - date, _ := time.Parse(dateLayout, splitline[0]) - weight, _ := strconv.ParseFloat(splitline[1], 64) if trend == 0.0 { trend = weight } else { trend = math.Floor((trend+0.1*(weight-trend))*100) / 100 } - s = append(s, point{date, weight, trend}) + points = append(points, point{d, weight, trend}) + } - return s, nil + return points } type showFlags struct { @@ -138,11 +185,8 @@ type showFlags struct { } func show(args showFlags) error { - points, err := process(logfile) - if err != nil { - return err - } - dietpoints, _ := process(dietfile) + points := process(logfile) + dietpoints := process(dietfile) var to time.Time var from time.Time if args.to.date != nil { @@ -170,7 +214,7 @@ func show(args showFlags) error { YRange.min = math.Floor(YRange.min) - 1.0 YRange.max = math.Ceil(YRange.max) + 1.0 graph := graph(xticks(xRange), s1, s2, s3, YRange) - err = writeGraphToFile(graph) + err := writeGraphToFile(graph) if err != nil { return err } @@ -194,7 +238,7 @@ func showCommand() (goutil.CommandFlagsInit, goutil.CommandFunc) { func writeDietFile(from time.Time, initial float64, goal float64, change float64) error { date := from weight := initial - s := "#Date\tTarget\n" + s := "" s += fmt.Sprintf("%s\t%.1f\n", date.Format(dateLayout), weight) for weight > goal { date = date.AddDate(0, 0, 1)