1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165
|
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()
}
|