lazydocker/vendor/github.com/jesseduffield/asciigraph/asciigraph.go
2019-06-05 21:08:38 +10:00

165 lines
3.8 KiB
Go

package asciigraph
import (
"bytes"
"fmt"
"math"
"strings"
)
// Plot returns ascii graph for a series.
func Plot(series []float64, options ...Option) string {
var logMaximum float64
config := configure(config{
Offset: 3,
}, options)
if config.Width > 0 {
series = interpolateArray(series, config.Width)
}
minimum, maximum := minMaxFloat64Slice(series)
if config.Min != nil && *config.Min < minimum {
minimum = *config.Min
}
if config.Max != nil && *config.Max > maximum {
maximum = *config.Max
}
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
}
var min2 float64
var max2 float64
var ratio float64
padding := 0.0 // if we set height to 10 but min and max are both 0, we need 10 units of padding
if interval != 0 {
ratio = float64(config.Height) / interval
} else {
ratio = 1
if config.Height > 0 {
padding = float64(config.Height)
}
}
min2 = round(minimum * ratio)
max2 = round((maximum + padding) * 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 minimum == float64(0) && maximum == float64(0) {
logMaximum = float64(-1)
}
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++ {
var magnitude float64
if rows > 0 {
magnitude = maximum - (float64(y-intmin2) * interval / float64(rows))
} else {
magnitude = float64(y)
}
label := fmt.Sprintf("%*.*f", maxWidth+1, precision, magnitude)
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] = "╰"
plot[rows-y0][x+config.Offset] = "╮"
} else {
plot[rows-y1][x+config.Offset] = "╭"
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()
}