From 2f0eca1cc6a59f786a475debce7811664ce7451c Mon Sep 17 00:00:00 2001 From: "Sean E. Russell" Date: Tue, 26 Jan 2021 11:13:45 -0600 Subject: Fixes #161, CPU names are sorted strictly as strings rather than lexographically. --- termui/linegraph.go | 86 ++++++++++++++++++++++++++++++++++++++++++------ termui/linegraph_test.go | 60 +++++++++++++++++++++++++++++++++ 2 files changed, 136 insertions(+), 10 deletions(-) create mode 100644 termui/linegraph_test.go diff --git a/termui/linegraph.go b/termui/linegraph.go index fa8a3ae..941183f 100644 --- a/termui/linegraph.go +++ b/termui/linegraph.go @@ -3,6 +3,8 @@ package termui import ( "image" "sort" + "strconv" + "unicode" . "github.com/gizak/termui/v3" drawille "github.com/xxxserxxx/gotop/v4/termui/drawille-go" @@ -27,6 +29,8 @@ type LineGraph struct { LineColors map[string]Color LabelStyles map[string]Modifier DefaultLineColor Color + + seriesList numbered } func NewLineGraph() *LineGraph { @@ -54,18 +58,20 @@ func (self *LineGraph) Draw(buf *Buffer) { colors[i] = make([]Color, self.Inner.Dy()+2) } - // sort the series so that overlapping data will overlap the same way each time - seriesList := make([]string, len(self.Data)) - i := 0 - for seriesName := range self.Data { - seriesList[i] = seriesName - i++ + if len(self.seriesList) != len(self.Data) { + // sort the series so that overlapping data will overlap the same way each time + self.seriesList = make(numbered, len(self.Data)) + i := 0 + for seriesName := range self.Data { + self.seriesList[i] = seriesName + i++ + } + sort.Sort(self.seriesList) } - sort.Strings(seriesList) // draw lines in reverse order so that the first color defined in the colorscheme is on top - for i := len(seriesList) - 1; i >= 0; i-- { - seriesName := seriesList[i] + for i := len(self.seriesList) - 1; i >= 0; i-- { + seriesName := self.seriesList[i] seriesData := self.Data[seriesName] seriesLineColor, ok := self.LineColors[seriesName] if !ok { @@ -128,7 +134,7 @@ func (self *LineGraph) Draw(buf *Buffer) { maxWid := 0 xoff := 0 // X offset for additional columns of text yoff := 0 // Y offset for resetting column to top of widget - for i, seriesName := range seriesList { + for i, seriesName := range self.seriesList { if yoff+i+2 > self.Inner.Dy() { xoff += maxWid + 2 yoff = -i @@ -159,3 +165,63 @@ func (self *LineGraph) Draw(buf *Buffer) { } } + +// A string containing an integer +type numbered []string + +func (n numbered) Len() int { return len(n) } +func (n numbered) Swap(i, j int) { n[i], n[j] = n[j], n[i] } +func (n numbered) Less(i, j int) bool { + a := n[i] + b := n[j] + for i := 0; i < len(a); i++ { + ac := a[i] + if unicode.IsDigit(rune(ac)) { + j := i + 1 + for ; j < len(a); j++ { + if !unicode.IsDigit(rune(a[j])) { + break + } + if j >= len(b) { + return false + } + if !unicode.IsDigit(rune(b[j])) { + return false + } + } + an, err := strconv.Atoi(a[i:j]) + if err != nil { + return true + } + if j > len(b) { + return false + } + for ; j < len(b); j++ { + if !unicode.IsDigit(rune(b[j])) { + break + } + } + bn, err := strconv.Atoi(b[i:j]) + if err != nil { + return true + } + if an < bn { + return true + } else if bn < an { + return false + } + i = j + } + if i >= len(a) { + return true + } else if i >= len(b) { + return false + } + if ac < b[i] { + return true + } else if b[i] < ac { + return false + } + } + return true +} diff --git a/termui/linegraph_test.go b/termui/linegraph_test.go new file mode 100644 index 0000000..e8f8a6a --- /dev/null +++ b/termui/linegraph_test.go @@ -0,0 +1,60 @@ +package termui + +import "testing" +import "sort" + +func TestLess(t *testing.T) { + tests := []struct { + a, b string + e bool + }{ + {a: "abc", b: "def", e: true}, + {a: "abc", b: "abc", e: true}, + {a: "def", b: "abc", e: false}, + {a: "1", b: "10", e: true}, + {a: "1", b: "2", e: true}, + {a: "a2", b: "2", e: false}, + {a: "a2", b: "a10", e: true}, + {a: "a20", b: "a2", e: false}, + {a: "abc20", b: "def2", e: true}, + {a: "abc20", b: "abc2", e: false}, + {a: "abc20", b: "abc20", e: true}, + {a: "abc30", b: "abc20", e: false}, + {a: "abc20a", b: "abc20", e: false}, + {a: "abc20", b: "abc20a", e: true}, + {a: "abc20", b: "abc2a", e: false}, + {a: "abc20", b: "abc3a", e: false}, + {a: "abc20", b: "abc2abc", e: false}, + } + for _, k := range tests { + n := numbered([]string{k.a, k.b}) + g := n.Less(0, 1) + if g != k.e { + t.Errorf("%s < %s: expected %v, got %v", k.a, k.b, k.e, g) + } + } +} + +func TestSort(t *testing.T) { + tests := []struct { + in, ex numbered + }{ + { + in: numbered{"abc", "def", "abc", "abc", "def", "abc", "1", "10", "1", "2", "a2", "2", "a2", "a10", "a20", "a2", "abc20", "def2", "abc20", "abc2", "abc20", "abc20", "abc30", "abc20", "abc20a", "abc20", "abc20", "abc20a", "abc20", "abc2a"}, + ex: numbered{"1", "1", "2", "2", "10", "a2", "a2", "a2", "a10", "a20", "abc", "abc", "abc", "abc", "abc2", "abc2a", "abc20", "abc20", "abc20", "abc20", "abc20", "abc20", "abc20", "abc20", "abc20a", "abc20a", "abc30", "def", "def", "def2"}, + }, + { + in: numbered{"CPU12", "CPU11", "CPU9", "CPU3", "CPU4", "CPU0", "CPU6", "CPU7", "CPU8", "CPU5", "CPU10", "CPU1", "CPU2"}, + ex: numbered{"CPU0", "CPU1", "CPU2", "CPU3", "CPU4", "CPU5", "CPU6", "CPU7", "CPU8", "CPU9", "CPU10", "CPU11", "CPU12"}, + }, + } + + for _, k := range tests { + sort.Sort(k.in) + for i, v := range k.in { + if v != k.ex[i] { + t.Errorf("failed to properly sort\n\texpected: %v\n\tgot: %v", k.ex, k.in) + } + } + } +} -- cgit v1.2.3