-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathrender.go
More file actions
203 lines (172 loc) · 5.59 KB
/
Copy pathrender.go
File metadata and controls
203 lines (172 loc) · 5.59 KB
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
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
package main
import (
"fmt"
"strings"
"github.com/charmbracelet/lipgloss"
)
// ── Unicode character sets ────────────────────────────────────────────
// braille encodes two vertical bar levels (0-4) per cell: braille[left*5+right]
const braille = " ⢀⢠⢰⢸⡀⣀⣠⣰⣸⡄⣄⣤⣴⣼⡆⣆⣦⣶⣾⡇⣇⣧⣷⣿"
// barFill is a slim lower-height block: bars read as a distinct, lighter
// element and cannot be confused with the braille sparklines below them.
const barFill = "▆"
// trackRune is the empty portion of a bar: a quiet dotted track.
const trackRune = "·"
// ── Status palette ────────────────────────────────────────────────────
//
// Muted steel fill with htop-like positional status accents. A filled bar
// cell is colored by its POSITION in the bar, not by the metric value: the
// bar stays calm until the fill actually reaches the hot zone, and then
// only the hot cells change color.
var (
steelStyle = rgbStyle(95, 135, 175) // #5f87af base fill
amberStyle = rgbStyle(215, 166, 95) // #d7a65f warning accent
redStyle = rgbStyle(215, 95, 95) // #d75f5f critical accent
trackStyle = rgbStyle(50, 50, 60) // dotted empty track
)
// tierStyles is indexed by statusTier.
var tierStyles = [3]lipgloss.Style{steelStyle, amberStyle, redStyle}
// statusTier maps a percentage to a color tier: 0 steel, 1 amber (>= 75),
// 2 red (>= 90).
func statusTier(pct float64) int {
switch {
case pct >= 90:
return 2
case pct >= 75:
return 1
default:
return 0
}
}
// barCellTier returns the color tier for the filled cell at position i of a
// bar that is width cells wide.
func barCellTier(i, width int) int {
return statusTier(float64(i) * 100 / float64(width))
}
func rgbStyle(r, g, b uint8) lipgloss.Style {
return lipgloss.NewStyle().Foreground(lipgloss.Color(fmt.Sprintf("#%02x%02x%02x", r, g, b)))
}
// ── Bar chart renderer ────────────────────────────────────────────────
func renderBar(value, maximum float64, width int) string {
if width <= 0 {
return ""
}
pct := 0.0
if maximum > 0 {
pct = value / maximum * 100
if pct > 100 {
pct = 100
}
if pct < 0 {
pct = 0
}
}
// The slim fill has no fractional-width variants, so the fill is rounded
// to the nearest whole cell instead of drawing a sub-cell tip.
filled := int(pct/100*float64(width) + 0.5)
if filled > width {
filled = width
}
var sb strings.Builder
// Paint filled cells in runs of a single tier to keep escape codes down.
for start := 0; start < filled; {
tier := barCellTier(start, width)
end := start + 1
for end < filled && barCellTier(end, width) == tier {
end++
}
sb.WriteString(tierStyles[tier].Render(strings.Repeat(barFill, end-start)))
start = end
}
if empty := width - filled; empty > 0 {
sb.WriteString(trackStyle.Render(strings.Repeat(trackRune, empty)))
}
return sb.String()
}
// ── Braille sparkline renderer ────────────────────────────────────────
func normalizeToLevels(value, vmin, vmax float64, total int) int {
if vmax <= vmin || value <= vmin {
return 0
}
if value >= vmax {
return total
}
t := (value - vmin) / (vmax - vmin)
level := int(t * float64(total))
if level < 1 {
level = 1
}
if level > total {
level = total
}
return level
}
// renderMultilineSparkline draws history as braille columns. Each column is
// colored by its value relative to colorScale: >= 90% red, >= 75% amber,
// otherwise the steel base — the same status thresholds as the bars.
func renderMultilineSparkline(history []float64, width, rows int, vmin, vmax float64, colorScale float64) []string {
result := make([]string, rows)
if width <= 0 {
return result
}
needed := width * 2
samples := make([]float64, needed)
if len(history) >= needed {
copy(samples, history[len(history)-needed:])
} else {
copy(samples[needed-len(history):], history)
}
totalLevels := rows * 4
brailleRunes := []rune(braille)
emptyStyle := rgbStyle(35, 35, 35)
rowBuilders := make([]strings.Builder, rows)
for i := 0; i < width; i++ {
vl := samples[i*2]
vr := samples[i*2+1]
fillL := normalizeToLevels(vl, vmin, vmax, totalLevels)
fillR := normalizeToLevels(vr, vmin, vmax, totalLevels)
isEmpty := fillL == 0 && fillR == 0
avg := (vl + vr) / 2
pct := 0.0
if colorScale > 0 {
pct = avg / colorScale * 100
}
colStyle := tierStyles[statusTier(pct)]
for r := 0; r < rows; r++ {
levelsBelow := (rows - 1 - r) * 4
ll := fillL - levelsBelow
rl := fillR - levelsBelow
if ll < 0 {
ll = 0
}
if ll > 4 {
ll = 4
}
if rl < 0 {
rl = 0
}
if rl > 4 {
rl = 4
}
if isEmpty || (ll == 0 && rl == 0) {
rowBuilders[r].WriteString(emptyStyle.Render(string(brailleRunes[0])))
} else {
rowBuilders[r].WriteString(colStyle.Render(string(brailleRunes[ll*5+rl])))
}
}
}
for i := range result {
result[i] = rowBuilders[i].String()
}
return result
}
// ── Formatting helpers ────────────────────────────────────────────────
func fmtGB(b int64) string {
return fmt.Sprintf("%.1fGB", float64(b)/float64(1024*1024*1024))
}
func fmtMHz(mhz int) string {
if mhz >= 1000 {
return fmt.Sprintf("%.2fGHz", float64(mhz)/1000)
}
return fmt.Sprintf("%dMHz", mhz)
}