summaryrefslogtreecommitdiff
path: root/chart/chart.go
diff options
context:
space:
mode:
authorMartin Kagamino Lehoux <martin@lehoux.net>2026-08-05 15:09:44 +0200
committerMartin Kagamino Lehoux <martin@lehoux.net>2026-08-05 15:09:44 +0200
commit8f829816e35a41844808d1d338fd02dcc0320981 (patch)
tree659ca819a3a2f617b4620f6980a221240806b1cf /chart/chart.go
parentb6ca97fe994c46737e0aa5c73e8b3d924ddaecd2 (diff)
refactor: Extract CLI and chart packages
Diffstat (limited to 'chart/chart.go')
-rw-r--r--chart/chart.go81
1 files changed, 81 insertions, 0 deletions
diff --git a/chart/chart.go b/chart/chart.go
new file mode 100644
index 0000000..a185378
--- /dev/null
+++ b/chart/chart.go
@@ -0,0 +1,81 @@
+package chart
+
+import (
+ "fmt"
+ "image/color"
+
+ "github.com/martinlehoux/biking_home/mountain_pass"
+ "github.com/martinlehoux/biking_home/ride"
+ "github.com/martinlehoux/kagamigo/kcore"
+ "gonum.org/v1/plot"
+ "gonum.org/v1/plot/plotter"
+ "gonum.org/v1/plot/vg"
+)
+
+func Render(r ride.Ride, climbs []ride.Climb, crossings []mountain_pass.Crossing, output string) {
+ p := plot.New()
+ p.Title.Text = "Ride profile"
+ p.X.Label.Text = "Distance (km)"
+ p.Y.Label.Text = "Elevation (m)"
+
+ maxElev := 0.0
+ elevation := make(plotter.XYs, len(r.Points()))
+ for i, point := range r.Points() {
+ elevation[i].X = point.DistanceM / 1000
+ elevation[i].Y = point.ElevationM
+ if point.ElevationM > maxElev {
+ maxElev = point.ElevationM
+ }
+ }
+
+ for _, climb := range climbs {
+ x0 := climb.Start().DistanceM / 1000
+ x1 := climb.End().DistanceM / 1000
+ fill, err := plotter.NewPolygon(plotter.XYs{
+ {X: x0, Y: 0},
+ {X: x1, Y: 0},
+ {X: x1, Y: maxElev},
+ {X: x0, Y: maxElev},
+ })
+ kcore.Expect(err, "failed to create climb band")
+ fill.Color = color.RGBA{R: 230, G: 160, B: 0, A: 70}
+ fill.LineStyle.Width = 0
+ p.Add(fill)
+ p.Add(plotLabels(plotter.XY{X: (x0 + x1) / 2, Y: climb.Top().ElevationM * 1.01}, climbLabel(climb)))
+ }
+
+ for _, crossing := range crossings {
+ x := crossing.RideDistanceM / 1000
+ line, err := plotter.NewLine(plotter.XYs{
+ {X: x, Y: 0},
+ {X: x, Y: maxElev},
+ })
+ kcore.Expect(err, "failed to create pass marker")
+ line.LineStyle.Dashes = []vg.Length{vg.Points(4), vg.Points(2)}
+ p.Add(line)
+ p.Add(plotLabels(plotter.XY{X: x, Y: maxElev * 0.96},
+ fmt.Sprintf("%s %dm", crossing.Pass.Name, crossing.Pass.Elevation)))
+ }
+
+ elevLine, err := plotter.NewLine(elevation)
+ kcore.Expect(err, "failed to create elevation line")
+ p.Add(elevLine)
+
+ kcore.Expect(p.Save(10*vg.Inch, 4*vg.Inch, output), "failed to save chart")
+}
+
+func climbLabel(climb ride.Climb) string {
+ if climb.Name != "" {
+ return climb.Name
+ }
+ return fmt.Sprintf("%s %d", ride.Category(climb.Score()), int(climb.Score()))
+}
+
+func plotLabels(xy plotter.XY, label string) *plotter.Labels {
+ labels, err := plotter.NewLabels(plotter.XYLabels{
+ XYs: plotter.XYs{xy},
+ Labels: []string{label},
+ })
+ kcore.Expect(err, "failed to create labels")
+ return labels
+}