pax_global_header00006660000000000000000000000064134601277420014520gustar00rootroot0000000000000052 comment=abcec7d90b5128c62a14d8d5b99fa04d3bcec626 golang-github-guptarohit-asciigraph-0.4.1/000077500000000000000000000000001346012774200205455ustar00rootroot00000000000000golang-github-guptarohit-asciigraph-0.4.1/.gitignore000066400000000000000000000003151346012774200225340ustar00rootroot00000000000000# Binaries for programs and plugins *.exe *.exe~ *.dll *.so *.dylib # Test binary, build with `go test -c` *.test # Output of the go coverage tool, specifically when used with LiteIDE *.out # ide .idea golang-github-guptarohit-asciigraph-0.4.1/.travis.yml000066400000000000000000000004101346012774200226510ustar00rootroot00000000000000language: go go: - "1.x" - "1.8.x" - "1.9.x" - "1.10.x" - master install: - go get github.com/mattn/goveralls - go get golang.org/x/tools/cmd/cover script: - go test -v -race ./. - goveralls -service=travis-ci notifications: email: false golang-github-guptarohit-asciigraph-0.4.1/LICENSE000066400000000000000000000027471346012774200215640ustar00rootroot00000000000000BSD 3-Clause License Copyright (c) 2018, Rohit Gupta All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. golang-github-guptarohit-asciigraph-0.4.1/README.rst000066400000000000000000000062621346012774200222420ustar00rootroot00000000000000.. -*-restructuredtext-*- asciigraph =========== .. image:: https://travis-ci.org/guptarohit/asciigraph.svg?branch=master :target: https://travis-ci.org/guptarohit/asciigraph :alt: Build status .. image:: https://goreportcard.com/badge/github.com/guptarohit/asciigraph :target: https://goreportcard.com/report/github.com/guptarohit/asciigraph :alt: Go Report Card .. image:: https://coveralls.io/repos/github/guptarohit/asciigraph/badge.svg?branch=master :target: https://coveralls.io/github/guptarohit/asciigraph?branch=master :alt: Coverage Status .. image:: https://godoc.org/github.com/guptarohit/asciigraph?status.svg :target: https://godoc.org/github.com/guptarohit/asciigraph :alt: GoDoc .. image:: https://img.shields.io/badge/licence-BSD-blue.svg :target: https://github.com/guptarohit/asciigraph/blob/master/LICENSE :alt: License | Go package to make lightweight ASCII line graphs ╭┈╯. .. image:: https://user-images.githubusercontent.com/7895001/41509956-b1b2b3d0-7279-11e8-9d19-d7dea17d5e44.png Installation ------------ :: go get github.com/guptarohit/asciigraph Usage ----- Basic graph ^^^^^^^^^^^ .. code:: go package main import ( "fmt" "github.com/guptarohit/asciigraph" ) func main() { data := []float64{3, 4, 9, 6, 2, 4, 5, 8, 5, 10, 2, 7, 2, 5, 6} graph := asciigraph.Plot(data) fmt.Println(graph) } Running this example would render the following graph: :: 10.00 ┤ ╭╮ 9.00 ┤ ╭╮ ││ 8.00 ┤ ││ ╭╮││ 7.00 ┤ ││ ││││╭╮ 6.00 ┤ │╰╮ ││││││ ╭ 5.00 ┤ │ │ ╭╯╰╯│││╭╯ 4.00 ┤╭╯ │╭╯ ││││ 3.00 ┼╯ ││ ││││ 2.00 ┤ ╰╯ ╰╯╰╯ .. Command line interface ---------------------- This package also brings a small utility for command line usage. Assuming ``$GOPATH/bin`` is in your ``$PATH``, simply ``go get`` it then install CLI. CLI Installation ^^^^^^^^^^^^^^^^ :: go install github.com/guptarohit/asciigraph/cmd/asciigraph Feed it data points via stdin: :: $ seq 1 72 | asciigraph -h 10 -c "plot data from stdin" 72.00 ┼ 65.55 ┤ ╭──── 59.09 ┤ ╭──────╯ 52.64 ┤ ╭──────╯ 46.18 ┤ ╭──────╯ 39.73 ┤ ╭──────╯ 33.27 ┤ ╭───────╯ 26.82 ┤ ╭──────╯ 20.36 ┤ ╭──────╯ 13.91 ┤ ╭──────╯ 7.45 ┤ ╭──────╯ 1.00 ┼──╯ plot data from stdin .. Acknowledgement ---------------- This package is golang port of library `asciichart `_ written by `@kroitor `_. Contributing ------------ Feel free to make a pull request! :octocat: golang-github-guptarohit-asciigraph-0.4.1/asciigraph.go000066400000000000000000000062501346012774200232110ustar00rootroot00000000000000package asciigraph import ( "bytes" "fmt" "math" "strings" ) // Plot returns ascii graph for a series. func Plot(series []float64, options ...Option) string { config := configure(config{ Offset: 3, }, options) if config.Width > 0 { series = interpolateArray(series, config.Width) } minimum, maximum := minMaxFloat64Slice(series) interval := math.Abs(maximum - minimum) if config.Height <= 0 { if int(interval) <= 0 { config.Height = int(interval * math.Pow10(int(math.Ceil(-math.Log10(interval))))) } else { config.Height = int(interval) } } if config.Offset <= 0 { config.Offset = 3 } ratio := float64(config.Height) / interval min2 := round(minimum * ratio) max2 := round(maximum * ratio) intmin2 := int(min2) intmax2 := int(max2) rows := int(math.Abs(float64(intmax2 - intmin2))) width := len(series) + config.Offset var plot [][]string // initialise empty 2D grid for i := 0; i < rows+1; i++ { var line []string for j := 0; j < width; j++ { line = append(line, " ") } plot = append(plot, line) } precision := 2 logMaximum := math.Log10(math.Max(math.Abs(maximum), math.Abs(minimum))) //to find number of zeros after decimal if logMaximum < 0 { // negative log if math.Mod(logMaximum, 1) != 0 { // non-zero digits after decimal precision = precision + int(math.Abs(logMaximum)) } else { precision = precision + int(math.Abs(logMaximum)-1.0) } } else if logMaximum > 2 { precision = 0 } maxNumLength := len(fmt.Sprintf("%0.*f", precision, maximum)) minNumLength := len(fmt.Sprintf("%0.*f", precision, minimum)) maxWidth := int(math.Max(float64(maxNumLength), float64(minNumLength))) // axis and labels for y := intmin2; y < intmax2+1; y++ { label := fmt.Sprintf("%*.*f", maxWidth+1, precision, maximum-(float64(y-intmin2)*interval/float64(rows))) w := y - intmin2 h := int(math.Max(float64(config.Offset)-float64(len(label)), 0)) plot[w][h] = label if y == 0 { plot[w][config.Offset-1] = "┼" } else { plot[w][config.Offset-1] = "┤" } } y0 := int(round(series[0]*ratio) - min2) var y1 int plot[rows-y0][config.Offset-1] = "┼" // first value for x := 0; x < len(series)-1; x++ { // plot the line y0 = int(round(series[x+0]*ratio) - float64(intmin2)) y1 = int(round(series[x+1]*ratio) - float64(intmin2)) if y0 == y1 { plot[rows-y0][x+config.Offset] = "─" } else { if y0 > y1 { plot[rows-y1][x+config.Offset] = "╰" } else { plot[rows-y1][x+config.Offset] = "╭" } if y0 > y1 { plot[rows-y0][x+config.Offset] = "╮" } else { plot[rows-y0][x+config.Offset] = "╯" } start := int(math.Min(float64(y0), float64(y1))) + 1 end := int(math.Max(float64(y0), float64(y1))) for y := start; y < end; y++ { plot[rows-y][x+config.Offset] = "│" } } } // join columns var lines bytes.Buffer for h, horizontal := range plot { if h != 0 { lines.WriteRune('\n') } for _, v := range horizontal { lines.WriteString(v) } } // add caption if not empty if config.Caption != "" { lines.WriteRune('\n') lines.WriteString(strings.Repeat(" ", config.Offset+maxWidth+2)) lines.WriteString(config.Caption) } return lines.String() } golang-github-guptarohit-asciigraph-0.4.1/asciigraph_test.go000066400000000000000000000147151346012774200242550ustar00rootroot00000000000000package asciigraph import ( "fmt" "testing" ) func TestPlot(t *testing.T) { cases := []struct { data []float64 opts []Option expected string }{ { []float64{2, 1, 1, 2, -2, 5, 7, 11, 3, 7, 1}, nil, ` 11.00 ┤ ╭╮ 10.00 ┤ ││ 9.00 ┼ ││ 8.00 ┤ ││ 7.00 ┤ ╭╯│╭╮ 6.00 ┤ │ │││ 5.00 ┤ ╭╯ │││ 4.00 ┤ │ │││ 3.00 ┤ │ ╰╯│ 2.00 ┼╮ ╭╮│ │ 1.00 ┤╰─╯││ ╰ 0.00 ┤ ││ -1.00 ┤ ││ -2.00 ┤ ╰╯ `}, { []float64{2, 1, 1, 2, -2, 5, 7, 11, 3, 7, 4, 5, 6, 9, 4, 0, 6, 1, 5, 3, 6, 2}, []Option{Caption("Plot using asciigraph.")}, ` 11.00 ┤ ╭╮ 10.00 ┤ ││ 9.00 ┼ ││ ╭╮ 8.00 ┤ ││ ││ 7.00 ┤ ╭╯│╭╮ ││ 6.00 ┤ │ │││ ╭╯│ ╭╮ ╭╮ 5.00 ┤ ╭╯ │││╭╯ │ ││╭╮││ 4.00 ┤ │ ││╰╯ ╰╮││││││ 3.00 ┤ │ ╰╯ ││││╰╯│ 2.00 ┼╮ ╭╮│ ││││ ╰ 1.00 ┤╰─╯││ ││╰╯ 0.00 ┤ ││ ╰╯ -1.00 ┤ ││ -2.00 ┤ ╰╯ Plot using asciigraph.`}, { []float64{.2, .1, .2, 2, -.9, .7, .91, .3, .7, .4, .5}, []Option{Caption("Plot using asciigraph.")}, ` 2.00 ┤ ╭╮ ╭╮ 0.55 ┼──╯│╭╯╰─── -0.90 ┤ ╰╯ Plot using asciigraph.`}, { []float64{2, 1, 1, 2, -2, 5, 7, 11, 3, 7, 1}, []Option{Height(4), Offset(3)}, ` 11.00 ┤ ╭╮ 7.75 ┼ ╭─╯│╭╮ 4.50 ┼╮ ╭╮│ ╰╯│ 1.25 ┤╰─╯││ ╰ -2.00 ┤ ╰╯ `}, { []float64{.453, .141, .951, .251, .223, .581, .771, .191, .393, .617, .478}, nil, ` 0.95 ┤ ╭╮ 0.85 ┤ ││ ╭╮ 0.75 ┤ ││ ││ 0.65 ┤ ││ ╭╯│ ╭╮ 0.55 ┤ ││ │ │ │╰ 0.44 ┼╮││ │ │╭╯ 0.34 ┤│││ │ ││ 0.24 ┤││╰─╯ ╰╯ 0.14 ┤╰╯ `}, { []float64{.01, .004, .003, .0042, .0083, .0033, 0.0079}, nil, ` 0.010 ┼╮ 0.009 ┤│ 0.008 ┤│ ╭╮╭ 0.007 ┤│ │││ 0.006 ┤│ │││ 0.005 ┤│ │││ 0.004 ┤╰╮╭╯││ 0.003 ┤ ╰╯ ╰╯ `}, { []float64{192, 431, 112, 449, -122, 375, 782, 123, 911, 1711, 172}, []Option{Height(10)}, ` 1711 ┤ ╭╮ 1528 ┼ ││ 1344 ┤ ││ 1161 ┤ ││ 978 ┤ ╭╯│ 794 ┤ ╭╮│ │ 611 ┤ │││ │ 428 ┤╭╮╭╮╭╯││ │ 245 ┼╯╰╯││ ╰╯ ╰ 61 ┤ ││ -122 ┤ ╰╯ `}, { []float64{0.3189989805, 0.149949026, 0.30142492354, 0.195129182935, 0.3142492354, 0.1674974513, 0.3142492354, 0.1474974513, 0.3047974513}, []Option{Width(30), Height(5), Caption("Plot with custom height & width.")}, ` 0.32 ┼╮ ╭─╮ ╭╮ ╭ 0.29 ┤╰╮ ╭─╮ ╭╯ │ ╭╯│ │ 0.26 ┤ │ ╭╯ ╰╮ ╭╯ ╰╮ ╭╯ ╰╮ ╭╯ 0.23 ┤ ╰╮ ╭╯ ╰╮│ ╰╮╭╯ ╰╮ ╭╯ 0.20 ┤ ╰╮│ ╰╯ ╰╯ │╭╯ 0.16 ┤ ╰╯ ╰╯ Plot with custom height & width.`}, { []float64{ 0, 0, 0, 0, 1.5, 0, 0, -0.5, 9, -3, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1.5, 0, 0, -0.5, 8, -3, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1.5, 0, 0, -0.5, 10, -3, 0, 0, 1, 2, 1, 0, 0, 0, 0, }, []Option{Offset(10), Height(10), Caption("I'm a doctor, not an engineer.")}, ` 10.00 ┤ ╭╮ 8.70 ┤ ╭╮ ││ 7.40 ┼ ││ ╭╮ ││ 6.10 ┤ ││ ││ ││ 4.80 ┤ ││ ││ ││ 3.50 ┤ ││ ││ ││ 2.20 ┤ ││ ╭╮ ││ ╭╮ ││ ╭╮ 0.90 ┤ ╭╮ ││ ╭╯╰╮ ╭╮ ││ ╭╯╰╮ ╭╮ ││ ╭╯╰╮ -0.40 ┼───╯╰──╯│╭─╯ ╰───────╯╰──╯│╭─╯ ╰───────╯╰──╯│╭─╯ ╰─── -1.70 ┤ ││ ││ ││ -3.00 ┤ ╰╯ ╰╯ ╰╯ I'm a doctor, not an engineer.`}, { []float64{-5, -2, -3, -4, 0, -5, -6, -7, -8, 0, -9, -3, -5, -2, -9, -3, -1}, nil, ` 0.00 ┤ ╭╮ ╭╮ -1.00 ┤ ││ ││ ╭ -2.00 ┤╭╮ ││ ││ ╭╮ │ -3.00 ┤│╰╮││ ││╭╮││╭╯ -4.00 ┤│ ╰╯│ │││││││ -5.00 ┼╯ ╰╮ │││╰╯││ -6.00 ┤ ╰╮ │││ ││ -7.00 ┤ ╰╮│││ ││ -8.00 ┤ ╰╯││ ││ -9.00 ┼ ╰╯ ╰╯ `}, { []float64{-0.000018527, -0.021, -.00123, .00000021312, -.0434321234, -.032413241234, .0000234234}, []Option{Height(5),Width(45)}, ` 0.000 ┼─╮ ╭────────╮ ╭ -0.008 ┤ ╰──╮ ╭──╯ ╰─╮ ╭─╯ -0.017 ┤ ╰─────╯ ╰╮ ╭─╯ -0.025 ┤ ╰─╮ ╭─╯ -0.034 ┤ ╰╮ ╭────╯ -0.042 ┼ ╰───╯ `}, } for i := range cases { name := fmt.Sprintf("%d", i) t.Run(name, func(t *testing.T) { c := cases[i] actual := Plot(c.data, c.opts...) if actual != c.expected { conf := configure(config{}, c.opts) t.Errorf("Plot(%f, %#v)", c.data, conf) t.Logf("expected:\n%s\n", c.expected) } t.Logf("actual:\n%s\n", actual) }) } } golang-github-guptarohit-asciigraph-0.4.1/cmd/000077500000000000000000000000001346012774200213105ustar00rootroot00000000000000golang-github-guptarohit-asciigraph-0.4.1/cmd/asciigraph/000077500000000000000000000000001346012774200234225ustar00rootroot00000000000000golang-github-guptarohit-asciigraph-0.4.1/cmd/asciigraph/main.go000066400000000000000000000025601346012774200247000ustar00rootroot00000000000000package main import ( "bufio" "flag" "fmt" "log" "os" "strconv" "github.com/guptarohit/asciigraph" ) var ( height uint width uint offset uint = 3 caption string ) func main() { flag.Usage = func() { fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0]) fmt.Fprintf(os.Stderr, " %s [options]\n", os.Args[0]) fmt.Fprintf(os.Stderr, "Options:\n") flag.PrintDefaults() fmt.Fprintf(os.Stderr, "%s expects data points from stdin. Invalid values are logged to stderr.\n", os.Args[0]) } flag.UintVar(&height, "h", height, "`height` in text rows, 0 for auto-scaling") flag.UintVar(&width, "w", width, "`width` in columns, 0 for auto-scaling") flag.UintVar(&offset, "o", offset, "`offset` in columns, for the label") flag.StringVar(&caption, "c", caption, "`caption` for the graph") flag.Parse() data := make([]float64, 0, 64) s := bufio.NewScanner(os.Stdin) s.Split(bufio.ScanWords) for s.Scan() { word := s.Text() p, err := strconv.ParseFloat(word, 64) if err != nil { log.Printf("ignore %q: cannot parse value", word) continue } data = append(data, p) } if err := s.Err(); err != nil { log.Fatal(err) } if len(data) == 0 { log.Fatal("no data") } plot := asciigraph.Plot(data, asciigraph.Height(int(height)), asciigraph.Width(int(width)), asciigraph.Offset(int(offset)), asciigraph.Caption(caption)) fmt.Println(plot) } golang-github-guptarohit-asciigraph-0.4.1/examples/000077500000000000000000000000001346012774200223635ustar00rootroot00000000000000golang-github-guptarohit-asciigraph-0.4.1/examples/SineCurve.go000066400000000000000000000033621346012774200246210ustar00rootroot00000000000000package main import ( "fmt" "math" "github.com/guptarohit/asciigraph" ) func main() { var data []float64 // sine curve for i := 0; i < 105; i++ { data = append(data, 15*math.Sin(float64(i)*((math.Pi*4)/120.0))) } graph := asciigraph.Plot(data, asciigraph.Height(10)) fmt.Println(graph) // Output: // 15.00 ┤ ╭────────╮ ╭────────╮ // 12.00 ┤ ╭──╯ ╰──╮ ╭──╯ ╰──╮ // 9.00 ┤ ╭──╯ ╰─╮ ╭──╯ ╰─╮ // 6.00 ┤ ╭─╯ ╰──╮ ╭─╯ ╰──╮ // 3.00 ┤╭─╯ ╰─╮ ╭─╯ ╰─╮ // 0.00 ┼╯ ╰╮ ╭╯ ╰╮ // -3.00 ┤ ╰─╮ ╭─╯ ╰─╮ // -6.00 ┤ ╰─╮ ╭──╯ ╰─╮ // -9.00 ┤ ╰──╮ ╭─╯ ╰──╮ // -12.00 ┤ ╰──╮ ╭──╯ ╰──╮ // -15.00 ┤ ╰────────╯ ╰─── } golang-github-guptarohit-asciigraph-0.4.1/options.go000066400000000000000000000025421346012774200225720ustar00rootroot00000000000000package asciigraph import ( "strings" ) // Option represents a configuration setting. type Option interface { apply(c *config) } // config holds various graph options type config struct { Width, Height int Offset int Caption string } // An optionFunc applies an option. type optionFunc func(*config) // apply implements the Option interface. func (of optionFunc) apply(c *config) { of(c) } func configure(defaults config, options []Option) *config { for _, o := range options { o.apply(&defaults) } return &defaults } // Width sets the graphs width. By default, the width of the graph is // determined by the number of data points. If the value given is a // positive number, the data points are interpolated on the x axis. // Values <= 0 reset the width to the default value. func Width(w int) Option { return optionFunc(func(c *config) { if w > 0 { c.Width = w } else { c.Width = 0 } }) } // Height sets the graphs height. func Height(h int) Option { return optionFunc(func(c *config) { if h > 0 { c.Height = h } else { c.Height = 0 } }) } // Offset sets the graphs offset. func Offset(o int) Option { return optionFunc(func(c *config) { c.Offset = o }) } // Caption sets the graphs caption. func Caption(caption string) Option { return optionFunc(func(c *config) { c.Caption = strings.TrimSpace(caption) }) } golang-github-guptarohit-asciigraph-0.4.1/utils.go000066400000000000000000000023741346012774200222420ustar00rootroot00000000000000package asciigraph import "math" func minMaxFloat64Slice(v []float64) (min, max float64) { min = math.Inf(1) max = math.Inf(-1) if len(v) == 0 { panic("Empty slice") } for _, e := range v { if e < min { min = e } if e > max { max = e } } return } func round(input float64) float64 { if math.IsNaN(input) { return math.NaN() } sign := 1.0 if input < 0 { sign = -1 input *= -1 } _, decimal := math.Modf(input) var rounded float64 if decimal >= 0.5 { rounded = math.Ceil(input) } else { rounded = math.Floor(input) } return rounded * sign } func linearInterpolate(before, after, atPoint float64) float64 { return before + (after-before)*atPoint } func interpolateArray(data []float64, fitCount int) []float64 { var interpolatedData []float64 springFactor := float64(len(data)-1) / float64(fitCount-1) interpolatedData = append(interpolatedData, data[0]) for i := 1; i < fitCount-1; i++ { spring := float64(i) * springFactor before := math.Floor(spring) after := math.Ceil(spring) atPoint := spring - before interpolatedData = append(interpolatedData, linearInterpolate(data[int(before)], data[int(after)], atPoint)) } interpolatedData = append(interpolatedData, data[len(data)-1]) return interpolatedData }