summaryrefslogtreecommitdiff
path: root/internal/tui/dashboard/sparkline.go
blob: 1531ca66756b0a13650a2dd009352ace6a10c778 (plain)
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
package dashboard

import "math"

var sparkChars = []rune("▁▂▃▄▅▆▇█")

func renderSparkline(data []float64, width int) string {
	if len(data) == 0 || width <= 0 {
		return ""
	}

	samples := sampleForWidth(data, width)
	min, max := minMax(samples)
	if min == max {
		return repeatRune('▄', len(samples))
	}

	out := make([]rune, len(samples))
	scale := float64(len(sparkChars) - 1)
	denom := max - min
	for i, value := range samples {
		idx := int(math.Round((value - min) / denom * scale))
		if idx < 0 {
			idx = 0
		}
		if idx >= len(sparkChars) {
			idx = len(sparkChars) - 1
		}
		out[i] = sparkChars[idx]
	}
	return string(out)
}

func sampleForWidth(data []float64, width int) []float64 {
	if width >= len(data) {
		return append([]float64(nil), data...)
	}
	if width == 1 {
		return []float64{data[len(data)-1]}
	}

	last := len(data) - 1
	samples := make([]float64, width)
	for i := 0; i < width; i++ {
		idx := int(math.Round(float64(i) * float64(last) / float64(width-1)))
		samples[i] = data[idx]
	}
	return samples
}

func minMax(values []float64) (float64, float64) {
	min := values[0]
	max := values[0]
	for _, v := range values[1:] {
		if v < min {
			min = v
		}
		if v > max {
			max = v
		}
	}
	return min, max
}

func repeatRune(r rune, count int) string {
	if count <= 0 {
		return ""
	}
	out := make([]rune, count)
	for i := range out {
		out[i] = r
	}
	return string(out)
}