From b54694ed123688f67574a7e46cdfed1de4968fd1 Mon Sep 17 00:00:00 2001 From: Xabier Larrakoetxea Date: Wed, 17 Apr 2019 13:42:05 +0200 Subject: [PATCH] LineChart defaults to 0 mix and max when these are NaN Signed-off-by: Xabier Larrakoetxea --- widgets/linechart/linechart.go | 26 ++++- widgets/linechart/linechart_test.go | 160 ++++++++++++++++++++-------- 2 files changed, 136 insertions(+), 50 deletions(-) diff --git a/widgets/linechart/linechart.go b/widgets/linechart/linechart.go index 4d6bf6b..e1d2d0f 100644 --- a/widgets/linechart/linechart.go +++ b/widgets/linechart/linechart.go @@ -57,7 +57,7 @@ func newSeriesValues(values []float64) *seriesValues { v := make([]float64, len(values)) copy(v, values) - min, max := numbers.MinMax(v) + min, max := minMax(v) return &seriesValues{ values: v, min: min, @@ -176,8 +176,9 @@ func (lc *LineChart) yMinMax() (float64, float64) { maximums = append(maximums, lc.opts.yAxisCustomScale.max) } - min, _ := numbers.MinMax(minimums) - _, max := numbers.MinMax(maximums) + min, _ := minMax(minimums) + _, max := minMax(maximums) + return min, max } @@ -530,3 +531,22 @@ func (lc *LineChart) maxXValue() int { } return maxLen - 1 } + +const ( + defMin = 0 + defMax = 0 +) + +// minMax is a wrapper around numbers.MinMax that controls +// the output if the values are NaN and sets defaults if it's +// the case. +func minMax(values []float64) (x, y float64) { + min, max := numbers.MinMax(values) + if math.IsNaN(min) { + min = defMin + } + if math.IsNaN(max) { + max = defMax + } + return min, max +} diff --git a/widgets/linechart/linechart_test.go b/widgets/linechart/linechart_test.go index dcb11e4..d3ddbb5 100644 --- a/widgets/linechart/linechart_test.go +++ b/widgets/linechart/linechart_test.go @@ -960,53 +960,6 @@ func TestLineChartDraws(t *testing.T) { return ft }, }, - { - desc: "more values than capacity, X rescales with NaN values ignored", - canvas: image.Rect(0, 0, 11, 10), - writes: func(lc *LineChart) error { - return lc.Series("first", []float64{0, 1, 2, 3, 4, 5, 6, 7, math.NaN(), math.NaN(), math.NaN(), math.NaN(), 12, 13, 14, 15, 16, 17, 18, 19}) - }, - wantCapacity: 12, - want: func(size image.Point) *faketerm.Terminal { - ft := faketerm.MustNew(size) - c := testcanvas.MustNew(ft.Area()) - - // Y and X axis. - lines := []draw.HVLine{ - {Start: image.Point{4, 0}, End: image.Point{4, 8}}, - {Start: image.Point{4, 8}, End: image.Point{10, 8}}, - } - testdraw.MustHVLines(c, lines) - - // Value labels. - testdraw.MustText(c, "0", image.Point{3, 7}) - testdraw.MustText(c, "9.92", image.Point{0, 3}) - testdraw.MustText(c, "0", image.Point{5, 9}) - testdraw.MustText(c, "14", image.Point{9, 9}) - - // Braille line. - graphAr := image.Rect(5, 0, 11, 8) - bc := testbraille.MustNew(graphAr) - testdraw.MustBrailleLine(bc, image.Point{0, 31}, image.Point{1, 29}) - testdraw.MustBrailleLine(bc, image.Point{1, 29}, image.Point{1, 28}) - testdraw.MustBrailleLine(bc, image.Point{1, 28}, image.Point{2, 26}) - testdraw.MustBrailleLine(bc, image.Point{2, 26}, image.Point{2, 25}) - testdraw.MustBrailleLine(bc, image.Point{2, 25}, image.Point{3, 23}) - testdraw.MustBrailleLine(bc, image.Point{3, 23}, image.Point{3, 21}) - testdraw.MustBrailleLine(bc, image.Point{3, 21}, image.Point{4, 20}) - testdraw.MustBrailleLine(bc, image.Point{7, 12}, image.Point{8, 10}) - testdraw.MustBrailleLine(bc, image.Point{8, 10}, image.Point{8, 8}) - testdraw.MustBrailleLine(bc, image.Point{8, 8}, image.Point{9, 7}) - testdraw.MustBrailleLine(bc, image.Point{9, 7}, image.Point{9, 5}) - testdraw.MustBrailleLine(bc, image.Point{9, 5}, image.Point{10, 4}) - testdraw.MustBrailleLine(bc, image.Point{10, 4}, image.Point{10, 2}) - testdraw.MustBrailleLine(bc, image.Point{10, 2}, image.Point{11, 0}) - testbraille.MustCopyTo(bc, c) - - testcanvas.MustApply(c, ft) - return ft - }, - }, { desc: "more values than capacity, X unscaled", opts: []Option{ @@ -1570,6 +1523,119 @@ func TestLineChartDraws(t *testing.T) { testdraw.MustBrailleLine(bc, image.Point{0, 31}, image.Point{26, 0}) testbraille.MustCopyTo(bc, c) + testcanvas.MustApply(c, ft) + return ft + }, + }, + { + desc: "all NaN values", + canvas: image.Rect(0, 0, 20, 10), + writes: func(lc *LineChart) error { + return lc.Series("first", []float64{math.NaN(), math.NaN(), math.NaN(), math.NaN(), math.NaN()}) + }, + wantCapacity: 36, + want: func(size image.Point) *faketerm.Terminal { + ft := faketerm.MustNew(size) + c := testcanvas.MustNew(ft.Area()) + + // Y and X axis. + lines := []draw.HVLine{ + {Start: image.Point{1, 0}, End: image.Point{1, 8}}, + {Start: image.Point{1, 8}, End: image.Point{19, 8}}, + } + testdraw.MustHVLines(c, lines) + + // Value labels. + testdraw.MustText(c, "0", image.Point{0, 7}) + testdraw.MustText(c, "0", image.Point{2, 9}) + testdraw.MustText(c, "1", image.Point{6, 9}) + testdraw.MustText(c, "2", image.Point{10, 9}) + testdraw.MustText(c, "3", image.Point{14, 9}) + testdraw.MustText(c, "4", image.Point{18, 9}) + + testcanvas.MustApply(c, ft) + return ft + }, + }, + { + desc: "first and last NaN values", + canvas: image.Rect(0, 0, 28, 10), + writes: func(lc *LineChart) error { + return lc.Series("first", []float64{math.NaN(), math.NaN(), 100, 150, math.NaN()}) + }, + wantCapacity: 44, + want: func(size image.Point) *faketerm.Terminal { + ft := faketerm.MustNew(size) + c := testcanvas.MustNew(ft.Area()) + + // Y and X axis. + lines := []draw.HVLine{ + {Start: image.Point{5, 0}, End: image.Point{5, 8}}, + {Start: image.Point{5, 8}, End: image.Point{27, 8}}, + } + testdraw.MustHVLines(c, lines) + + // Value labels. + testdraw.MustText(c, "0", image.Point{4, 7}) + testdraw.MustText(c, "77.44", image.Point{0, 3}) + testdraw.MustText(c, "0", image.Point{6, 9}) + testdraw.MustText(c, "1", image.Point{11, 9}) + testdraw.MustText(c, "2", image.Point{16, 9}) + testdraw.MustText(c, "3", image.Point{22, 9}) + testdraw.MustText(c, "4", image.Point{27, 9}) + + graphAr := image.Rect(6, 0, 25, 8) + bc := testbraille.MustNew(graphAr) + testdraw.MustBrailleLine(bc, image.Point{21, 10}, image.Point{32, 0}) + testbraille.MustCopyTo(bc, c) + + testcanvas.MustApply(c, ft) + return ft + }, + }, + { + desc: "more values than capacity, X rescales with NaN values ignored", + canvas: image.Rect(0, 0, 11, 10), + writes: func(lc *LineChart) error { + return lc.Series("first", []float64{0, 1, 2, 3, 4, 5, 6, 7, math.NaN(), math.NaN(), math.NaN(), math.NaN(), 12, 13, 14, 15, 16, 17, 18, 19}) + }, + wantCapacity: 12, + want: func(size image.Point) *faketerm.Terminal { + ft := faketerm.MustNew(size) + c := testcanvas.MustNew(ft.Area()) + + // Y and X axis. + lines := []draw.HVLine{ + {Start: image.Point{4, 0}, End: image.Point{4, 8}}, + {Start: image.Point{4, 8}, End: image.Point{10, 8}}, + } + testdraw.MustHVLines(c, lines) + + // Value labels. + testdraw.MustText(c, "0", image.Point{3, 7}) + testdraw.MustText(c, "9.92", image.Point{0, 3}) + testdraw.MustText(c, "0", image.Point{5, 9}) + testdraw.MustText(c, "14", image.Point{9, 9}) + + // Braille line. + graphAr := image.Rect(5, 0, 11, 8) + bc := testbraille.MustNew(graphAr) + testdraw.MustBrailleLine(bc, image.Point{0, 31}, image.Point{1, 29}) + testdraw.MustBrailleLine(bc, image.Point{1, 29}, image.Point{1, 28}) + testdraw.MustBrailleLine(bc, image.Point{1, 28}, image.Point{2, 26}) + testdraw.MustBrailleLine(bc, image.Point{2, 26}, image.Point{2, 25}) + testdraw.MustBrailleLine(bc, image.Point{2, 25}, image.Point{3, 23}) + testdraw.MustBrailleLine(bc, image.Point{3, 23}, image.Point{3, 21}) + testdraw.MustBrailleLine(bc, image.Point{3, 21}, image.Point{4, 20}) + testdraw.MustBrailleLine(bc, image.Point{7, 12}, image.Point{8, 10}) + testdraw.MustBrailleLine(bc, image.Point{8, 10}, image.Point{8, 8}) + testdraw.MustBrailleLine(bc, image.Point{8, 8}, image.Point{9, 7}) + testdraw.MustBrailleLine(bc, image.Point{9, 7}, image.Point{9, 5}) + testdraw.MustBrailleLine(bc, image.Point{9, 5}, image.Point{10, 4}) + testdraw.MustBrailleLine(bc, image.Point{10, 4}, image.Point{10, 2}) + testdraw.MustBrailleLine(bc, image.Point{10, 2}, image.Point{11, 0}) + testbraille.MustCopyTo(bc, c) + testcanvas.MustApply(c, ft) return ft },