diff options
| author | Lukasz Kasprzak <lukas@labunix.xyz> | 2026-08-25 16:09:31 +0200 |
|---|---|---|
| committer | Lukasz Kasprzak <lukas@labunix.xyz> | 2026-08-25 16:09:31 +0200 |
| commit | fb1d31fdcd5d5c8b31bb098c34143996d47e618f (patch) | |
| tree | 41fd165f467a99fef784e05759c71d89605a85ef /internal/render/render_test.go | |
| parent | a7941bb92df67686b018fbb601cbf413ea909d14 (diff) | |
| download | prognosis-fb1d31fdcd5d5c8b31bb098c34143996d47e618f.tar.gz prognosis-fb1d31fdcd5d5c8b31bb098c34143996d47e618f.zip | |
chart: label a narrow axis in decimals
A span of 1.2 degrees rounded to whole degrees printed "15" against both the
middle and bottom rows, so the axis asserted that two rows drawn at different
heights were the same temperature. It reads as a rendering fault, and the scale
becomes useless exactly when the chart is at its most magnified.
Below a two degree span the labels carry one decimal. Two degrees is where
five rows put adjacent labels half a degree apart, which still rounds apart;
below that they collide. A decimal label that would not fit the gutter -- well
below freezing, where the minus sign costs a column -- falls back to whole
degrees, which is wrong but legible and beats shearing the column.
The gutter widens by one to hold "16.0°".
Diffstat (limited to 'internal/render/render_test.go')
| -rw-r--r-- | internal/render/render_test.go | 83 |
1 files changed, 83 insertions, 0 deletions
diff --git a/internal/render/render_test.go b/internal/render/render_test.go index d06fc7b..4879855 100644 --- a/internal/render/render_test.go +++ b/internal/render/render_test.go @@ -403,3 +403,86 @@ func TestNoSpeciesIsPrivileged(t *testing.T) { t.Errorf("grass at zero must be dropped like any other species:\n%s", out) } } + +// A narrow range cannot be labelled in whole degrees: rounding makes adjacent +// rows read the same, so the axis claims two different temperatures are equal. +func TestNarrowRangeGetsDecimalAxisLabels(t *testing.T) { + cfg := testConfig() + cfg.Graph = true + var rows []openmeteo.Row + for i, temp := range []float64{15.7, 16.0, 16.0, 15.2, 14.8, 15.1, 15.2, 15.2} { + r := row(0, temp, 3, 0, 0) + r.When = time.Date(2026, 8, 25, 15+i, 0, 0, 0, time.UTC) + rows = append(rows, r) + } + out := Render(view(rows...), cfg, 80, false) + + var labels []string + for _, l := range strings.Split(out, "\n") { + if i := strings.Index(l, "│"); i > 0 { + if lbl := strings.TrimSpace(l[:i]); lbl != "" && lbl != "rain" { + labels = append(labels, lbl) + } + } + } + if len(labels) < 3 { + t.Fatalf("expected three axis labels, got %v\n%s", labels, out) + } + seen := map[string]bool{} + for _, l := range labels { + if seen[l] { + t.Errorf("axis label %q repeats; a 1.2 degree span needs decimals:\n%s", l, out) + } + seen[l] = true + if !strings.Contains(l, ".") { + t.Errorf("label %q should carry a decimal for a narrow range", l) + } + } +} + +// A wide range stays in whole degrees: decimals there are noise. +func TestWideRangeKeepsWholeDegreeLabels(t *testing.T) { + cfg := testConfig() + cfg.Graph = true + var rows []openmeteo.Row + for i, temp := range []float64{5, 12, 18, 24, 28, 22, 14, 7} { + r := row(0, temp, 3, 0, 0) + r.When = time.Date(2026, 8, 25, 15+i, 0, 0, 0, time.UTC) + rows = append(rows, r) + } + out := Render(view(rows...), cfg, 80, false) + for _, l := range strings.Split(out, "\n") { + if i := strings.Index(l, "│"); i > 0 { + lbl := strings.TrimSpace(l[:i]) + if strings.Contains(lbl, ".") { + t.Errorf("wide range should use whole degrees, got %q", lbl) + } + } + } +} + +func TestAxisLabelPrecisionAndWidth(t *testing.T) { + cases := []struct { + lo, hi float64 + maxWidth int + wantDot bool + why string + }{ + {14.8, 16.0, 5, true, "1.2 degrees needs decimals"}, + {12, 28, 5, false, "16 degrees does not"}, + {15, 17, 5, false, "exactly the threshold stays whole"}, + {-20.4, -19.6, 5, false, "decimals would not fit, so fall back"}, + } + for _, c := range cases { + f := tempLabeller(c.lo, c.hi, c.maxWidth) + got := f(c.lo) + if strings.Contains(got, ".") != c.wantDot { + t.Errorf("lo=%v hi=%v gave %q; %s", c.lo, c.hi, got, c.why) + } + for _, v := range []float64{c.lo, c.hi} { + if w := DisplayWidth(f(v)); w > c.maxWidth { + t.Errorf("label %q is %d cells, over the %d-cell gutter", f(v), w, c.maxWidth) + } + } + } +} |
