├── .github └── FUNDING.yml ├── .gitignore ├── LICENSE ├── README.md ├── bar.go ├── examples └── basic.go ├── go.mod ├── go.sum ├── hist.go ├── img.go ├── plot.go └── util.go /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [bsipos] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bsipos/thist/a1ef5cf2e7158549f9cba8f9849ad87c2e92f39d/.gitignore -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright © 2019 Botond Sipos 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | thist - a go package for calculating online histograms with plotting to the terminal and images 2 | =============================================================================================== 3 | [![Documentation](https://godoc.org/github.com/bsipos/thist?status.svg)](http://godoc.org/github.com/bsipos/thist) 4 | [![Go Report Card](https://goreportcard.com/badge/github.com/bsipos/thist)](https://goreportcard.com/report/github.com/bsipos/thist) 5 | [![Join the chat at https://gitter.im/bsiposkj/thist](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/bsiposkj/thist?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 6 | 7 | Check out the `watch` subcommands from the [csvtk](https://github.com/shenwei356/csvtk) and [seqkit](https://github.com/shenwei356/seqkit) tools to see the code in action. 8 | 9 | Example 10 | ------- 11 | 12 | ```go 13 | package main 14 | 15 | import ( 16 | "fmt" 17 | "github.com/bsipos/thist" 18 | "math/rand" 19 | "time" 20 | ) 21 | 22 | // randStream return a channel filled with endless normal random values 23 | func randStream() chan float64 { 24 | c := make(chan float64) 25 | go func() { 26 | for { 27 | c <- rand.NormFloat64() 28 | } 29 | }() 30 | return c 31 | } 32 | 33 | func main() { 34 | // create new histogram 35 | h := thist.NewHist(nil, "Example histogram", "auto", -1, true) 36 | c := randStream() 37 | 38 | i := 0 39 | for { 40 | // add data point to hsitogram 41 | h.Update(<-c) 42 | if i%50 == 0 { 43 | // draw histogram 44 | fmt.Println(h.Draw()) 45 | time.Sleep(time.Second) 46 | } 47 | i++ 48 | } 49 | } 50 | 51 | ``` 52 | 53 | [![demo video](http://img.youtube.com/vi/7mrs1QGDyys/0.jpg)](http://www.youtube.com/watch?v=7mrs1QGDyys) 54 | 55 | TODO 56 | ---- 57 | 58 | - Add more details on online histogram generation. 59 | - Add separate object for online estimation of moments. 60 | - Maybe add tcell as a back-end? 61 | -------------------------------------------------------------------------------- /bar.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2019 Botond Sipos 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package thist 22 | 23 | // BarSimple plots a bar plot on the terminal. Does not use unicode charcters. 24 | func BarSimple(x, y []float64, xlab, ylab []string, title string, info []string) string { 25 | if len(xlab) == 0 { 26 | xlab = AutoLabel(x, mean(absFloats(x))) 27 | } 28 | if len(ylab) == 0 { 29 | ylab = AutoLabel(y, mean(absFloats(y))) 30 | } 31 | return Plot(x, y, xlab, ylab, title, info, "#", "@", " ", "_", "|", "-", "|") 32 | } 33 | 34 | // Bar plots a bar plot on the terminal. It makes use of unicode characters. 35 | func Bar(x, y []float64, xlab, ylab []string, title string, info []string) string { 36 | if len(xlab) == 0 { 37 | xlab = AutoLabel(x, mean(absFloats(x))) 38 | } 39 | if len(ylab) == 0 { 40 | ylab = AutoLabel(y, mean(absFloats(y))) 41 | } 42 | return Plot(x, y, xlab, ylab, title, info, "\u2588", "\u2591", " ", "_", "\u2502", "\u2500", "\u2524") 43 | } 44 | -------------------------------------------------------------------------------- /examples/basic.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/bsipos/thist" 6 | "math/rand" 7 | "time" 8 | ) 9 | 10 | // randStream return a channel filled with endless normal random values 11 | func randStream() chan float64 { 12 | c := make(chan float64) 13 | go func() { 14 | for { 15 | c <- rand.NormFloat64() 16 | } 17 | }() 18 | return c 19 | } 20 | 21 | func main() { 22 | // create new histogram 23 | h := thist.NewHist(nil, "Example histogram", "auto", -1, true) 24 | c := randStream() 25 | 26 | i := 0 27 | for { 28 | // add data point to hsitogram 29 | h.Update(<-c) 30 | if i%50 == 0 { 31 | // draw histogram 32 | fmt.Println(h.Draw()) 33 | time.Sleep(time.Second) 34 | } 35 | i++ 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/bsipos/thist 2 | 3 | go 1.12 4 | 5 | require ( 6 | github.com/ajstarks/deck v0.0.0-20190923150139-1e575512713c // indirect 7 | github.com/ajstarks/openvg v0.0.0-20190803144842-bb08bf5efa0c // indirect 8 | github.com/ajstarks/svgo v0.0.0-20190826172357-de52242f3d65 // indirect 9 | github.com/disintegration/gift v1.2.1 // indirect 10 | github.com/edsrzf/mmap-go v1.0.0 // indirect 11 | github.com/fogleman/gg v1.3.0 // indirect 12 | github.com/jung-kurt/gofpdf v1.12.1 // indirect 13 | github.com/kr/pty v1.1.8 // indirect 14 | github.com/phpdave11/gofpdi v1.0.8 // indirect 15 | github.com/remyoudompheng/bigfft v0.0.0-20190728182440-6a916e37a237 // indirect 16 | github.com/rogpeppe/go-internal v1.3.2 // indirect 17 | github.com/stretchr/objx v0.2.0 // indirect 18 | github.com/stretchr/testify v1.4.0 // indirect 19 | golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392 20 | golang.org/x/exp v0.0.0-20190919035709-81c71964d733 // indirect 21 | golang.org/x/image v0.0.0-20190910094157-69e4b8554b2a // indirect 22 | golang.org/x/mobile v0.0.0-20190923204409-d3ece3b6da5f // indirect 23 | golang.org/x/net v0.0.0-20190923162816-aa69164e4478 // indirect 24 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e // indirect 25 | golang.org/x/sys v0.0.0-20190924062700-2aa67d56cdd7 // indirect 26 | golang.org/x/text v0.3.2 // indirect 27 | golang.org/x/tools v0.0.0-20190924052046-3ac2a5bbd98a // indirect 28 | gonum.org/v1/gonum v0.0.0-20190923210107-40fa6a493b3d // indirect 29 | gonum.org/v1/netlib v0.0.0-20190331212654-76723241ea4e // indirect 30 | gonum.org/v1/plot v0.0.0-20190615073203-9aa86143727f 31 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect 32 | honnef.co/go/tools v0.0.1-2019.2.3 // indirect 33 | modernc.org/fileutil v1.0.0 // indirect 34 | modernc.org/internal v1.0.0 // indirect 35 | modernc.org/ir v1.0.0 // indirect 36 | modernc.org/lex v1.0.0 // indirect 37 | modernc.org/lexer v1.0.0 // indirect 38 | modernc.org/strutil v1.1.0 // indirect 39 | ) 40 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9 h1:VpgP7xuJadIUuKccphEpTJnWhS2jkQyMt6Y7pJCD7fY= 2 | dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= 3 | github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= 4 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 5 | github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802 h1:1BDTz0u9nC3//pOCMdNH+CiXJVYJh5UQNCOBG7jbELc= 6 | github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= 7 | github.com/ajstarks/deck v0.0.0-20190907145448-75342179e36a h1:rnWaUiGCg+ty6p8zHZEVqXCZJZ1Ko+c7kkXaTSVewxk= 8 | github.com/ajstarks/deck v0.0.0-20190907145448-75342179e36a/go.mod h1:j3f/59diR4DorW5A78eDYvRkdrkh+nps4p5LA1Tl05U= 9 | github.com/ajstarks/deck v0.0.0-20190923150139-1e575512713c h1:hyGo/LIWxHHNcyP3mhYuaucDTtEIxSlRDf/qYntiSpo= 10 | github.com/ajstarks/deck v0.0.0-20190923150139-1e575512713c/go.mod h1:j3f/59diR4DorW5A78eDYvRkdrkh+nps4p5LA1Tl05U= 11 | github.com/ajstarks/openvg v0.0.0-20190803144842-bb08bf5efa0c h1:NFQRF5xWpIBuShkHfXENuxdOjKGtCrw9UcsVfqTbvjg= 12 | github.com/ajstarks/openvg v0.0.0-20190803144842-bb08bf5efa0c/go.mod h1:jpZHIkd4sQEgrzshrUQrRfv5OUMMq0w/Q1yK6ZYhUlk= 13 | github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af h1:wVe6/Ea46ZMeNkQjjBW6xcqyQA/j5e0D6GytH95g0gQ= 14 | github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= 15 | github.com/ajstarks/svgo v0.0.0-20190826172357-de52242f3d65 h1:kZegOsPGxfV9mM8WzfllNZOx3MvM5zItmhQlvITKVvA= 16 | github.com/ajstarks/svgo v0.0.0-20190826172357-de52242f3d65/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= 17 | github.com/boombuler/barcode v1.0.0 h1:s1TvRnXwL2xJRaccrdcBQMZxq6X7DvsMogtmJeHDdrc= 18 | github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= 19 | github.com/creack/pty v1.1.7 h1:6pwm8kMQKCmgUg0ZHTm5+/YvRK0s3THD/28+T6/kk4A= 20 | github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= 21 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 22 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 23 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 24 | github.com/disintegration/gift v1.2.1 h1:Y005a1X4Z7Uc+0gLpSAsKhWi4qLtsdEcMIbbdvdZ6pc= 25 | github.com/disintegration/gift v1.2.1/go.mod h1:Jh2i7f7Q2BM7Ezno3PhfezbR1xpUg9dUg3/RlKGr4HI= 26 | github.com/edsrzf/mmap-go v1.0.0 h1:CEBF7HpRnUCSJgGUb5h1Gm7e3VkmVDrR8lvWVLtrOFw= 27 | github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= 28 | github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90 h1:WXb3TSNmHp2vHoCroCIB1foO/yQ36swABL8aOVeDpgg= 29 | github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= 30 | github.com/fogleman/gg v1.3.0 h1:/7zJX8F6AaYQc57WQCyN9cAIz+4bCJGO9B+dyW29am8= 31 | github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= 32 | github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1 h1:QbL/5oDUmRBzO9/Z7Seo6zf912W/a6Sr4Eu0G/3Jho0= 33 | github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= 34 | github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= 35 | github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= 36 | github.com/google/renameio v0.1.0 h1:GOZbcHa3HfsPKPlmyPyN2KEohoMXOhdMbHrvbpl2QaA= 37 | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= 38 | github.com/jung-kurt/gofpdf v1.0.0/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= 39 | github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5 h1:PJr+ZMXIecYc1Ey2zucXdR73SMBtgjPgwa31099IMv0= 40 | github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= 41 | github.com/jung-kurt/gofpdf v1.12.1 h1:I0vzKelSMG8815qP4eHQFtfAKMZDJwCE+AhP8FgjxYk= 42 | github.com/jung-kurt/gofpdf v1.12.1/go.mod h1:PUFlk38sbwAJn0qocZnkWxDXLFa+Mqry8o6ilSOzWw8= 43 | github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg= 44 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 45 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= 46 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 47 | github.com/kr/pty v1.1.1 h1:VkoXIwSboBpnk99O/KFauAEILuNHv5DVFKZMBN/gUgw= 48 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 49 | github.com/kr/pty v1.1.8 h1:AkaSdXYQOWeaO3neb8EM634ahkXXe3jYbVh/F9lq+GI= 50 | github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= 51 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 52 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 53 | github.com/phpdave11/gofpdi v1.0.7 h1:k2oy4yhkQopCK+qW8KjCla0iU2RpDow+QUDmH9DDt44= 54 | github.com/phpdave11/gofpdi v1.0.7/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI= 55 | github.com/phpdave11/gofpdi v1.0.8 h1:9HRg0Z0qDfWeMU7ska+YNQ13RHxTxqP5KTg/dBl4o7c= 56 | github.com/phpdave11/gofpdi v1.0.8/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI= 57 | github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= 58 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 59 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 60 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 61 | github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uYEyJGbgTkfkS4+E/PavXkNJcbFIpEtjt2B0KDQ5+9M= 62 | github.com/remyoudompheng/bigfft v0.0.0-20190728182440-6a916e37a237 h1:HQagqIiBmr8YXawX/le3+O26N+vPPC1PtjaF3mwnook= 63 | github.com/remyoudompheng/bigfft v0.0.0-20190728182440-6a916e37a237/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= 64 | github.com/rogpeppe/go-internal v1.3.0 h1:RR9dF3JtopPvtkroDZuVD7qquD0bnHlKSqaQhgwt8yk= 65 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 66 | github.com/rogpeppe/go-internal v1.3.2 h1:XU784Pr0wdahMY2bYcyK6N1KuaRAdLtqD4qd8D18Bfs= 67 | github.com/rogpeppe/go-internal v1.3.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= 68 | github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58 h1:nlG4Wa5+minh3S9LVFtNoY+GVRiudA2e3EVfcCi3RCA= 69 | github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w= 70 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 71 | github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48= 72 | github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= 73 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 74 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 75 | github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= 76 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 77 | github.com/wayneashleyberry/terminal-dimensions v1.0.0 h1:LawtS1nqKjAfqrmKOzkcrDLAjSzh38lEhC401JPjQVA= 78 | github.com/wayneashleyberry/terminal-dimensions v1.0.0/go.mod h1:PW2XrtV6KmKOPhuf7wbtcmw1/IFnC39mryRET2XbxeE= 79 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 80 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 81 | golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5 h1:58fnuSXlxZmFdJyvtTFVmVhcMLU6v5fEb/ok4wyqtNU= 82 | golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 83 | golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7 h1:0hQKqeLdqlt5iIwVOBErRisrHJAN57yOiPRQItI20fU= 84 | golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 85 | golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392 h1:ACG4HJsFiNMf47Y4PeRoebLNy/2lXT9EtprMuTFWt1M= 86 | golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= 87 | golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 88 | golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 89 | golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 90 | golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 91 | golang.org/x/exp v0.0.0-20190312203227-4b39c73a6495/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= 92 | golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4= 93 | golang.org/x/exp v0.0.0-20190829153037-c13cbed26979 h1:Agxu5KLo8o7Bb634SVDnhIfpTvxmzUwhbYAzBvXt6h4= 94 | golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= 95 | golang.org/x/exp v0.0.0-20190919035709-81c71964d733 h1:1Y2c67YuKvbO9EobVoSRD2OVjd7wspa6bmE5qR2YTGg= 96 | golang.org/x/exp v0.0.0-20190919035709-81c71964d733/go.mod h1:lopKMxgphN5jWNwrkPRQU99WV/Hs5LrdgRBxZ5ELgOQ= 97 | golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81 h1:00VmoueYNlNz/aHIilyyQz/MHSqGoWJzpFv/HW8xpzI= 98 | golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= 99 | golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= 100 | golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= 101 | golang.org/x/image v0.0.0-20190902063713-cb417be4ba39/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= 102 | golang.org/x/image v0.0.0-20190910094157-69e4b8554b2a h1:gHevYm0pO4QUbwy8Dmdr01R5r1BuKtfYqRqF0h/Cbh0= 103 | golang.org/x/image v0.0.0-20190910094157-69e4b8554b2a/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= 104 | golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= 105 | golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= 106 | golang.org/x/mobile v0.0.0-20190910184405-b558ed863381 h1:URnJMZZIc5c2ki1FXIxz5064QwnhKkze2hHAWFbfzPw= 107 | golang.org/x/mobile v0.0.0-20190910184405-b558ed863381/go.mod h1:p895TfNkDgPEmEQrNiOtIl3j98d/tGU95djDj7NfyjQ= 108 | golang.org/x/mobile v0.0.0-20190923204409-d3ece3b6da5f h1:ggT/HnGy6BhrtNJIKu04aoGoKjhCr6THe0JvoaoThfk= 109 | golang.org/x/mobile v0.0.0-20190923204409-d3ece3b6da5f/go.mod h1:p895TfNkDgPEmEQrNiOtIl3j98d/tGU95djDj7NfyjQ= 110 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= 111 | golang.org/x/mod v0.1.0 h1:sfUMP1Gu8qASkorDVjnMuvgJzwFbTZSeXFiGBYAVdl4= 112 | golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= 113 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 114 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 115 | golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 116 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 117 | golang.org/x/net v0.0.0-20190909003024-a7b16738d86b h1:XfVGCX+0T4WOStkaOsJRllbsiImhB2jgVBGc9L0lPGc= 118 | golang.org/x/net v0.0.0-20190909003024-a7b16738d86b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 119 | golang.org/x/net v0.0.0-20190923162816-aa69164e4478 h1:l5EDrHhldLYb3ZRHDUhXF7Om7MvYXnkV9/iQNo1lX6g= 120 | golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 121 | golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU= 122 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 123 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY= 124 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 125 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 126 | golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 127 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 128 | golang.org/x/sys v0.0.0-20190602015325-4c4f7f33c9ed h1:uPxWBzB3+mlnjy9W58qY1j/cjyFjutgw/Vhan2zLy/A= 129 | golang.org/x/sys v0.0.0-20190602015325-4c4f7f33c9ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 130 | golang.org/x/sys v0.0.0-20190910064555-bbd175535a8b h1:3S2h5FadpNr0zUUCVZjlKIEYF+KaX/OBplTGo89CYHI= 131 | golang.org/x/sys v0.0.0-20190910064555-bbd175535a8b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 132 | golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 133 | golang.org/x/sys v0.0.0-20190924062700-2aa67d56cdd7 h1:9Vs0Vm0p/0tnWLBWn79aav6fpcxKjBZbd21Lhxzit4k= 134 | golang.org/x/sys v0.0.0-20190924062700-2aa67d56cdd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 135 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 136 | golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= 137 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 138 | golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 139 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 140 | golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 141 | golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 142 | golang.org/x/tools v0.0.0-20190606050223-4d9ae51c2468/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 143 | golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 144 | golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 145 | golang.org/x/tools v0.0.0-20190909214602-067311248421/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 146 | golang.org/x/tools v0.0.0-20190911022129-16c5e0f7d110 h1:6S6bidS7O4yAwA5ORRbRIjvNQ9tGbLd5e+LRIaTeVDQ= 147 | golang.org/x/tools v0.0.0-20190911022129-16c5e0f7d110/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 148 | golang.org/x/tools v0.0.0-20190924052046-3ac2a5bbd98a h1:DJzZ1GRmbjp7ihxzAN6UTVpVMi6k4CXZEr7A3wi2kRA= 149 | golang.org/x/tools v0.0.0-20190924052046-3ac2a5bbd98a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 150 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 h1:9zdDQZ7Thm29KFXgAX/+yaf3eVbP7djjWp/dXAppNCc= 151 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 152 | gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= 153 | gonum.org/v1/gonum v0.0.0-20190331200053-3d26580ed485/go.mod h1:2ltnJ7xHfj0zHS40VVPYEAAMTa3ZGguvHGBSJeRWqE0= 154 | gonum.org/v1/gonum v0.0.0-20190908220844-1d8f8b2ee4ce h1:EcGHlVD92aPgz3XO5I84fTgKh8VugjPdizlWKvazyoc= 155 | gonum.org/v1/gonum v0.0.0-20190908220844-1d8f8b2ee4ce/go.mod h1:9mxDZsDKxgMAuccQkewq682L+0eCu4dCN2yonUJTCLU= 156 | gonum.org/v1/gonum v0.0.0-20190923210107-40fa6a493b3d h1:76YcjS4MnGnP2WCvowHdydyWXEXQ/EsZkHjTUpB2ht8= 157 | gonum.org/v1/gonum v0.0.0-20190923210107-40fa6a493b3d/go.mod h1:9mxDZsDKxgMAuccQkewq682L+0eCu4dCN2yonUJTCLU= 158 | gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= 159 | gonum.org/v1/netlib v0.0.0-20190331212654-76723241ea4e h1:jRyg0XfpwWlhEV8mDfdNGBeSJM2fuyh9Yjrnd8kF2Ts= 160 | gonum.org/v1/netlib v0.0.0-20190331212654-76723241ea4e/go.mod h1:kS+toOQn6AQKjmKJ7gzohV1XkqsFehRA2FbsbkopSuQ= 161 | gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b h1:Qh4dB5D/WpoUUp3lSod7qgoyEHbDGPUWjIbnqdqqe1k= 162 | gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc= 163 | gonum.org/v1/plot v0.0.0-20190615073203-9aa86143727f h1:5+IdMldM5iTBk6wFBDtdVSSCaIPL922N3xbxmPE3Z1g= 164 | gonum.org/v1/plot v0.0.0-20190615073203-9aa86143727f/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc= 165 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 166 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 167 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= 168 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 169 | gopkg.in/errgo.v2 v2.1.0 h1:0vLT13EuvQ0hNvakwLuFZ/jYrLp5F3kcWHXdRggjCE8= 170 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 171 | gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= 172 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 173 | honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM= 174 | honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= 175 | modernc.org/cc v1.0.0 h1:nPibNuDEx6tvYrUAtvDTTw98rx5juGsa5zuDnKwEEQQ= 176 | modernc.org/cc v1.0.0/go.mod h1:1Sk4//wdnYJiUIxnW8ddKpaOJCF37yAdqYnkxUpaYxw= 177 | modernc.org/fileutil v1.0.0 h1:Z1AFLZwl6BO8A5NldQg/xTSjGLetp+1Ubvl4alfGx8w= 178 | modernc.org/fileutil v1.0.0/go.mod h1:JHsWpkrk/CnVV1H/eGlFf85BEpfkrp56ro8nojIq9Q8= 179 | modernc.org/golex v1.0.0 h1:wWpDlbK8ejRfSyi0frMyhilD3JBvtcx2AdGDnU+JtsE= 180 | modernc.org/golex v1.0.0/go.mod h1:b/QX9oBD/LhixY6NDh+IdGv17hgB+51fET1i2kPSmvk= 181 | modernc.org/internal v1.0.0 h1:XMDsFDcBDsibbBnHB2xzljZ+B1yrOVLEFkKL2u15Glw= 182 | modernc.org/internal v1.0.0/go.mod h1:VUD/+JAkhCpvkUitlEOnhpVxCgsBI90oTzSCRcqQVSM= 183 | modernc.org/ir v1.0.0 h1:5S08NkggW1cm/422E1oq9+DsywE5qxHzEC3QumMWuxw= 184 | modernc.org/ir v1.0.0/go.mod h1:wxK1nK3PS04CASoUY+HJr+FQywv4+D38y2sRrd71y7s= 185 | modernc.org/lex v1.0.0 h1:w0dxp18i1q+aSE7GkepvwzvVWTLoCIQ2oDgTFAV2JZU= 186 | modernc.org/lex v1.0.0/go.mod h1:G6rxMTy3cH2iA0iXL/HRRv4Znu8MK4higxph/lE7ypk= 187 | modernc.org/lexer v1.0.0 h1:D2xE6YTaH7aiEC7o/+rbx6qTAEr1uY83peKwkamIdQ0= 188 | modernc.org/lexer v1.0.0/go.mod h1:F/Dld0YKYdZCLQ7bD0USbWL4YKCyTDRDHiDTOs0q0vk= 189 | modernc.org/mathutil v1.0.0 h1:93vKjrJopTPrtTNpZ8XIovER7iCIH1QU7wNbOQXC60I= 190 | modernc.org/mathutil v1.0.0/go.mod h1:wU0vUrJsVWBZ4P6e7xtFJEhFSNsfRLJ8H458uRjg03k= 191 | modernc.org/strutil v1.0.0/go.mod h1:lstksw84oURvj9y3tn8lGvRxyRC1S2+g5uuIzNfIOBs= 192 | modernc.org/strutil v1.1.0 h1:+1/yCzZxY2pZwwrsbH+4T7BQMoLQ9QiBshRC9eicYsc= 193 | modernc.org/strutil v1.1.0/go.mod h1:lstksw84oURvj9y3tn8lGvRxyRC1S2+g5uuIzNfIOBs= 194 | modernc.org/xc v1.0.0 h1:7ccXrupWZIS3twbUGrtKmHS2DXY6xegFua+6O3xgAFU= 195 | modernc.org/xc v1.0.0/go.mod h1:mRNCo0bvLjGhHO9WsyuKVU4q0ceiDDDoEeWDJHrNx8I= 196 | rsc.io/pdf v0.1.1 h1:k1MczvYDUvJBe93bYd7wrZLLUEcLZAuF824/I4e5Xr4= 197 | rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= 198 | -------------------------------------------------------------------------------- /hist.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2019 Botond Sipos 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package thist 22 | 23 | import ( 24 | "fmt" 25 | terminal "golang.org/x/crypto/ssh/terminal" 26 | "math" 27 | "os" 28 | "sort" 29 | "strconv" 30 | "strings" 31 | ) 32 | 33 | // Hist is a struct holding the parameters and internal state of a histogram object. 34 | type Hist struct { 35 | Title string 36 | BinMode string 37 | MaxBins int 38 | NrBins int 39 | DataCount int 40 | DataMap map[float64]float64 41 | DataMin float64 42 | DataMax float64 43 | DataMean float64 44 | DataSd float64 45 | Normalize bool 46 | BinStart []float64 47 | BinEnd []float64 48 | Counts []float64 49 | m float64 50 | MaxPrecision float64 51 | Precision float64 52 | BinWidth float64 53 | Info string 54 | } 55 | 56 | // NewHist initilizes a new histogram object. If data is not nil the data points are processed and the state is updated. 57 | func NewHist(data []float64, title, binMode string, maxBins int, normalize bool) *Hist { 58 | h := &Hist{title, binMode, maxBins, 0, 0, make(map[float64]float64), math.NaN(), math.NaN(), math.NaN(), math.NaN(), normalize, []float64{}, []float64{}, []float64{}, math.NaN(), 14.0, 14.0, math.NaN(), ""} 59 | if h.BinMode == "" { 60 | h.BinMode = "termfit" 61 | } 62 | 63 | if len(data) > 0 { 64 | min, max := data[0], data[0] 65 | h.DataMean = data[0] 66 | h.DataSd = 0.0 67 | h.m = 0.0 68 | for _, d := range data { 69 | if d < min { 70 | min = d 71 | } 72 | if d > max { 73 | max = d 74 | } 75 | h.DataCount++ 76 | h.updateMoments(d) 77 | } 78 | h.DataMin = min 79 | h.DataMax = max 80 | h.BinStart, h.BinEnd, h.BinWidth = h.buildBins() 81 | h.updatePrecision() 82 | h.Counts = make([]float64, len(h.BinStart)) 83 | for _, v := range data { 84 | c := roundFloat64(v, h.Precision) 85 | h.DataMap[c]++ 86 | i := sort.SearchFloat64s(h.BinStart, c) - 1 87 | if i < 0 { 88 | i = 0 89 | } 90 | h.Counts[i]++ 91 | } 92 | h.updateInfo() 93 | } 94 | 95 | return h 96 | } 97 | 98 | // updateInfo updates the info string based on the current internal state. 99 | func (h *Hist) updateInfo() { 100 | digits := strconv.Itoa(int(h.Precision)) 101 | h.Info = fmt.Sprintf("Count: %d Mean: %."+digits+"f Stdev: %."+digits+"f Min: %."+digits+"f Max: %."+digits+"f Precision: %.0f Bins: %d\n", h.DataCount, h.DataMean, h.DataSd, h.DataMin, h.DataMax, h.Precision, len(h.BinStart)) 102 | } 103 | 104 | func (h *Hist) buildBins() ([]float64, []float64, float64) { 105 | var n int 106 | var w float64 107 | 108 | if h.DataMin == h.DataMax { 109 | n = 1 110 | w = 1 111 | } else if h.BinMode == "fixed" { 112 | n = h.MaxBins 113 | w = (h.DataMax - h.DataMin) / float64(n) 114 | } else if h.BinMode == "auto" || h.BinMode == "fit" || h.BinMode == "termfit" { 115 | w = scottsRule(h.DataCount, h.DataSd) 116 | n = int((h.DataMax - h.DataMin) / w) 117 | if n < 1 { 118 | n = 1 119 | } 120 | if h.BinMode == "fit" && n > h.MaxBins { 121 | n = h.MaxBins 122 | } 123 | if h.BinMode == "termfit" { 124 | tm, _, terr := terminal.GetSize(int(os.Stderr.Fd())) 125 | if terr != nil { 126 | tm = 80 127 | } 128 | tm -= 10 129 | if n > int(tm) { 130 | n = int(tm) 131 | } 132 | 133 | } 134 | w = (h.DataMax - h.DataMin) / float64(n) 135 | } 136 | 137 | s := make([]float64, n) 138 | e := make([]float64, n) 139 | 140 | for i := 0; i < n; i++ { 141 | s[i] = h.DataMin + float64(i)*w 142 | e[i] = h.DataMin + float64(i+1)*w 143 | } 144 | 145 | return s, e, w 146 | 147 | } 148 | 149 | // NormCounts returns the normalised counts for each bin. 150 | func (h *Hist) NormCounts() []float64 { 151 | res := make([]float64, len(h.Counts)) 152 | for i, c := range h.Counts { 153 | res[i] = c / float64(h.DataCount) / h.BinWidth 154 | } 155 | return res 156 | } 157 | 158 | // updateMoments calculates the new mean and sd of the dataset after adding a new data point p. 159 | func (h *Hist) updateMoments(p float64) { 160 | oldMean := h.DataMean 161 | h.DataMean += (p - h.DataMean) / float64(h.DataCount) 162 | h.m += (p - oldMean) * (p - h.DataMean) 163 | h.DataSd = math.Sqrt(h.m / float64(h.DataCount)) 164 | } 165 | 166 | // scottsRule calculates the number of histogram bins based on Scott's rule: 167 | // https://en.wikipedia.org/wiki/Histogram#Scott's_normal_reference_rule 168 | func scottsRule(n int, sd float64) float64 { 169 | h := (3.5 * sd) / math.Pow(float64(n), 1.0/3.0) 170 | return h 171 | } 172 | 173 | // Update adds a new data point and updates internal state. 174 | func (h *Hist) Update(p float64) { 175 | h.DataCount++ 176 | oldMin := h.DataMin 177 | oldMax := h.DataMax 178 | if math.IsNaN(h.DataMin) || p < h.DataMin { 179 | h.DataMin = p 180 | } 181 | if math.IsNaN(h.DataMax) || p > h.DataMax { 182 | h.DataMax = p 183 | } 184 | if h.DataCount == 1 { 185 | h.DataMean = p 186 | h.DataSd = 0.0 187 | h.m = 0.0 188 | h.BinStart, h.BinEnd, h.BinWidth = h.buildBins() 189 | h.updatePrecision() 190 | h.Counts = []float64{1.0} 191 | } else { 192 | h.updateMoments(p) 193 | h.updateInfo() 194 | } 195 | 196 | h.DataMap[roundFloat64(p, h.Precision)]++ 197 | 198 | if !math.IsNaN(oldMin) && p >= oldMin && !math.IsNaN(oldMax) && p <= oldMax { 199 | var i int 200 | if p == oldMin { 201 | i = 0 202 | } else if p == oldMax { 203 | i = len(h.Counts) - 1 204 | } else { 205 | i = sort.SearchFloat64s(h.BinStart, p) - 1 206 | if i < 0 { 207 | i = 0 208 | } 209 | } 210 | h.Counts[i]++ 211 | return 212 | } 213 | 214 | h.BinStart, h.BinEnd, h.BinWidth = h.buildBins() 215 | h.updatePrecision() 216 | newCounts := make([]float64, len(h.BinStart)) 217 | 218 | for v, c := range h.DataMap { 219 | i := sort.SearchFloat64s(h.BinStart, v) - 1 220 | if i < 0 { 221 | i = 0 222 | } 223 | newCounts[i] += c 224 | } 225 | 226 | h.Counts = newCounts 227 | 228 | } 229 | 230 | // updatePrecision claculates the precision to use for binnig based on the 231 | // bin width and the maximum allowed precision. 232 | func (h *Hist) updatePrecision() { 233 | h.Precision = math.Ceil(-math.Log10(h.BinWidth)) * 2.0 234 | if h.Precision > h.MaxPrecision { 235 | h.Precision = h.MaxPrecision 236 | } 237 | if h.Precision < 1.0 { 238 | h.Precision = 1.0 239 | } 240 | } 241 | 242 | // GetMode calculates the approximate mode of tehe dataset. 243 | func (h *Hist) GetMode() float64 { 244 | maxi, maxc := 0, 0.0 245 | for i, c := range h.NormCounts() { 246 | 247 | if c >= maxc { 248 | maxi, maxc = i, c 249 | } 250 | } 251 | return (h.BinStart[maxi] + h.BinEnd[maxi]) / 2 252 | } 253 | 254 | // Draw calls Bar to draw the hsitogram to the terminal. 255 | func (h *Hist) Draw() string { 256 | d := h.Counts 257 | if h.Normalize { 258 | d = h.NormCounts() 259 | } 260 | digits := strconv.Itoa(int(h.Precision)) 261 | modeStr := fmt.Sprintf(" ApproxMode: %."+digits+"f", h.GetMode()) 262 | info := strings.Split(strings.TrimRight(h.Info, "\n"), "\n") 263 | info[0] += modeStr 264 | return Bar(h.BinStart, d, []string{}, []string{}, h.Title, info) 265 | } 266 | 267 | // DrawSimple calls BarSimple to draw the hsitogram to the terminal. 268 | func (h *Hist) DrawSimple() string { 269 | d := h.Counts 270 | if h.Normalize { 271 | d = h.NormCounts() 272 | } 273 | digits := strconv.Itoa(int(h.Precision)) 274 | modeStr := fmt.Sprintf(" ApproxMode: %."+digits+"f", h.GetMode()) 275 | info := strings.Split(strings.TrimRight(h.Info, "\n"), "\n") 276 | info[0] += modeStr 277 | return BarSimple(h.BinStart, d, []string{}, []string{}, h.Title, info) 278 | } 279 | 280 | // Summary return a string summary of the internal state of a Hist object. 281 | func (h *Hist) Summary() string { 282 | res := "" // FIXME: TODO 283 | return res 284 | } 285 | 286 | // Dump prints the bins and counts to the standard output. 287 | func (h *Hist) Dump() string { 288 | res := "Bin\tBinStart\tBinEnd\tCount\n" 289 | 290 | for i, c := range h.Counts { 291 | res += fmt.Sprintf("%d\t%.4f\t%.4f\t%.0f\n", i, h.BinStart[i], h.BinEnd[i], c) 292 | } 293 | 294 | return res 295 | } 296 | -------------------------------------------------------------------------------- /img.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2019 Botond Sipos 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package thist 22 | 23 | import ( 24 | "fmt" 25 | "strconv" 26 | "strings" 27 | 28 | "gonum.org/v1/plot" 29 | "gonum.org/v1/plot/plotter" 30 | "gonum.org/v1/plot/plotutil" 31 | "gonum.org/v1/plot/vg" 32 | ) 33 | 34 | // SaveImage saves a histogram to an image file using gonum plot. 35 | func (h *Hist) SaveImage(f string) { 36 | data := plotter.Values(h.Counts) 37 | 38 | if h.Normalize { 39 | data = h.NormCounts() 40 | } 41 | 42 | p, err := plot.New() 43 | if err != nil { 44 | panic(err) 45 | } 46 | 47 | p.Title.Text = h.Title 48 | p.Y.Label.Text = "Count" 49 | if h.Normalize { 50 | p.Y.Label.Text = "Frequency" 51 | } 52 | 53 | bins := make([]plotter.HistogramBin, len(h.BinStart)) 54 | for i, binStart := range h.BinStart { 55 | bins[i] = plotter.HistogramBin{binStart, h.BinEnd[i], data[i]} 56 | } 57 | 58 | ph := &plotter.Histogram{ 59 | Bins: bins, 60 | Width: h.DataMax - h.DataMin, 61 | FillColor: plotutil.Color(2), 62 | LineStyle: plotter.DefaultLineStyle, 63 | } 64 | ph.LineStyle.Width = vg.Length(0.5) 65 | ph.Color = plotutil.Color(0) 66 | 67 | p.Add(ph) 68 | digits := strconv.Itoa(int(h.Precision)) 69 | modeStr := fmt.Sprintf(" ApproxMode: %."+digits+"f", h.GetMode()) 70 | info := strings.TrimRight(h.Info, "\n") + modeStr 71 | p.X.Label.Text = info 72 | 73 | if err := p.Save(11.69*vg.Inch, 8.27*vg.Inch, f); err != nil { 74 | panic(err) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /plot.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2019 Botond Sipos 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package thist 22 | 23 | import ( 24 | "fmt" 25 | terminal "golang.org/x/crypto/ssh/terminal" 26 | "os" 27 | "strconv" 28 | "strings" 29 | ) 30 | 31 | // Plot is a general plotting function for bar plots. It is used by Bar and BarSimple. 32 | func Plot(x, y []float64, xlab, ylab []string, title string, info []string, symbol, negSymbol, space, top, vbar, hbar, tvbar string) string { 33 | if len(x) == 0 { 34 | return "" 35 | } 36 | // Based on: http://pyinsci.blogspot.com/2009/10/ascii-histograms.html 37 | width, height, terr := terminal.GetSize(int(os.Stderr.Fd())) 38 | if terr != nil { 39 | width = 80 40 | height = 24 41 | } 42 | 43 | xll := stringsMaxLen(xlab) 44 | yll := stringsMaxLen(ylab) 45 | width -= yll + 1 46 | 47 | res := strings.Repeat(space, yll+1) + centerPad2Len(title, space, int(width)) + "\n" 48 | height -= 4 49 | height -= len(info) 50 | 51 | height -= xll + 1 52 | 53 | xf := xFactor(len(x), int(width)) 54 | if xf < 1 { 55 | xf = 1 56 | } 57 | 58 | if xll < xf-2 { 59 | height += xll - 1 60 | } 61 | 62 | ny := normalizeY(y, int(height)) 63 | 64 | block := strings.Repeat(symbol, xf) 65 | nblock := strings.Repeat(negSymbol, xf) 66 | if xf > 2 { 67 | block = vbar + strings.Repeat(symbol, xf-2) + vbar 68 | nblock = vbar + strings.Repeat(negSymbol, xf-2) + vbar 69 | } 70 | 71 | blank := strings.Repeat(space, xf) 72 | topBar := strings.Repeat(top, xf) 73 | 74 | for l := int(height); l > 0; l-- { 75 | if yll > 0 { 76 | found := false 77 | for i, t := range ny { 78 | if l == t { 79 | res += fmt.Sprintf("%-"+strconv.Itoa(yll)+"s"+tvbar, ylab[i]) 80 | found = true 81 | break 82 | } 83 | } 84 | if !found { 85 | res += strings.Repeat(space, yll) + vbar 86 | } 87 | } 88 | for _, c := range ny { 89 | if l == abs(c) { 90 | res += topBar 91 | } else if l < abs(c) { 92 | if c < 0 { 93 | res += nblock 94 | } else { 95 | res += block 96 | } 97 | } else { 98 | res += blank 99 | } 100 | } 101 | res += "\n" 102 | } 103 | 104 | if xll > 0 { 105 | res += strings.Repeat(space, yll) + vbar + strings.Repeat(hbar, int(width)) + "\n" 106 | if xll < xf-2 { 107 | res += strings.Repeat(space, yll) + vbar 108 | for _, xl := range xlab { 109 | res += vbar + rightPad2Len(xl, space, xf-1) 110 | } 111 | } else { 112 | for i := 0; i < xll; i++ { 113 | res += strings.Repeat(space, yll) + vbar 114 | for j := yll + 1; j < int(width); j++ { 115 | if (j-yll-1)%xf == 0 { 116 | bin := (j - yll - 1) / xf 117 | if bin < len(xlab) && i < len(xlab[bin]) { 118 | res += string(xlab[bin][i]) 119 | } else { 120 | res += space 121 | } 122 | } else { 123 | res += space 124 | } 125 | } 126 | res += "\n" 127 | } 128 | 129 | } 130 | } 131 | for _, il := range info { 132 | res += strings.Repeat(space, yll) + vbar + centerPad2Len(il, space, int(width)) + "\n" 133 | } 134 | return res 135 | } 136 | 137 | // normalizeY normalizes y values to a maximum height. 138 | func normalizeY(y []float64, height int) []int { 139 | max := max(y) 140 | res := make([]int, len(y)) 141 | 142 | for i, x := range y { 143 | res[i] = int(x / max * float64(height)) 144 | } 145 | return res 146 | } 147 | 148 | // xFactor. 149 | func xFactor(n int, width int) int { 150 | return int(width / n) 151 | } 152 | -------------------------------------------------------------------------------- /util.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2019 Botond Sipos 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package thist 22 | 23 | import ( 24 | "fmt" 25 | "math" 26 | "strconv" 27 | "strings" 28 | ) 29 | 30 | // max calculates the maximum of a float64 slice. 31 | func max(s []float64) float64 { 32 | if len(s) == 0 { 33 | return math.NaN() 34 | } 35 | 36 | max := s[0] 37 | for _, x := range s { 38 | if x > max { 39 | max = x 40 | } 41 | } 42 | return max 43 | } 44 | 45 | //min calculates the minimum of a float64 slice. 46 | func min(s []float64) float64 { 47 | if len(s) == 0 { 48 | return math.NaN() 49 | } 50 | 51 | max := s[0] 52 | for _, x := range s { 53 | if x < max { 54 | max = x 55 | } 56 | } 57 | return max 58 | } 59 | 60 | // mean calculates the mean of a float64 slice. 61 | func mean(s []float64) float64 { 62 | if len(s) == 0 { 63 | return math.NaN() 64 | } 65 | 66 | var sum float64 67 | for _, x := range s { 68 | sum += x 69 | } 70 | return sum / float64(len(s)) 71 | } 72 | 73 | // absFloats calculates the absolute value of a float64 slice. 74 | func absFloats(s []float64) []float64 { 75 | res := make([]float64, len(s)) 76 | for i, x := range s { 77 | res[i] = math.Abs(x) 78 | } 79 | return res 80 | } 81 | 82 | // abs calculates the absolute value of an integer. 83 | func abs(n int) int { 84 | if n < 0 { 85 | return -n 86 | } 87 | return n 88 | } 89 | 90 | // ClearScreen uses control characters to clear terminal. 91 | func ClearScreen() { 92 | fmt.Printf("\033[2J") 93 | } 94 | 95 | // ClearScreenString returns the control characters to clear terminal. 96 | func ClearScreenString() string { 97 | return "\033[2J" 98 | } 99 | 100 | // stringsMaxLen returns the length of a longest string in a slice. 101 | func stringsMaxLen(s []string) int { 102 | if len(s) == 0 { 103 | return 0 104 | } 105 | 106 | max := len(s[0]) 107 | for _, x := range s { 108 | if len(x) > max { 109 | max = len(x) 110 | } 111 | } 112 | return max 113 | 114 | } 115 | 116 | // AutoLabel generates automatic labeling based on heuristics-based rounding of the values in s. 117 | func AutoLabel(s []float64, m float64) []string { 118 | res := make([]string, len(s)) 119 | nf := false 120 | var digits int 121 | if min(s) < 0 { 122 | nf = true 123 | } 124 | if math.Abs(m) == 0 { 125 | digits = 5 126 | } else { 127 | digits = -int(math.Log10(math.Abs(m) / 5)) 128 | } 129 | if math.Abs(m) < 10 && digits < 3 { 130 | digits = 3 131 | } 132 | if digits <= 0 { 133 | digits = 1 134 | } 135 | if digits > 8 { 136 | digits = 8 137 | } 138 | dl := 0 139 | for _, x := range s { 140 | dg := digits + int(math.Log10(math.Abs(x)+1.0)) 141 | if dg < 0 { 142 | dg = 0 143 | } 144 | if dg > dl { 145 | dl = dg 146 | } 147 | } 148 | for i, x := range s { 149 | dg := dl - int(math.Log10(math.Abs(x)+1.0)) 150 | if dg < 0 { 151 | dg = 0 152 | } 153 | f := "%." + strconv.Itoa(dg) + "f" 154 | ff := f 155 | if nf && !(x < 0) { 156 | ff = " " + f 157 | } 158 | res[i] = fmt.Sprintf(ff, x) 159 | } 160 | return res 161 | } 162 | 163 | // roundFloat64 rounds a float value to the given precision. 164 | func roundFloat64(f float64, n float64) float64 { 165 | if n == 0.0 { 166 | return math.Round(f) 167 | } 168 | factor := math.Pow(10, float64(n)) 169 | return math.Round(f*factor) / factor 170 | } 171 | 172 | // leftPad2Len left pads a string to a given length. 173 | // https://github.com/DaddyOh/golang-samples/blob/master/pad.go 174 | func leftPad2Len(s string, padStr string, overallLen int) string { 175 | var padCountInt = 1 + ((overallLen - len(padStr)) / len(padStr)) 176 | var retStr = strings.Repeat(padStr, padCountInt) + s 177 | return retStr[(len(retStr) - overallLen):] 178 | } 179 | 180 | // rightPad2Len right pads a string to a given length. 181 | // https://github.com/DaddyOh/golang-samples/blob/master/pad.go 182 | func rightPad2Len(s string, padStr string, overallLen int) string { 183 | var padCountInt = 1 + ((overallLen - len(padStr)) / len(padStr)) 184 | var retStr = s + strings.Repeat(padStr, padCountInt) 185 | return retStr[:overallLen] 186 | } 187 | 188 | // centerPad2Len center pads a string to a given length. 189 | // https://www.socketloop.com/tutorials/golang-aligning-strings-to-right-left-and-center-with-fill-example 190 | func centerPad2Len(s string, fill string, n int) string { 191 | if len(s) >= n { 192 | return s 193 | } 194 | div := (n - len(s)) / 2 195 | 196 | return strings.Repeat(fill, div) + s + strings.Repeat(fill, div) 197 | } 198 | --------------------------------------------------------------------------------