v0.1.0 initial commit
This commit is contained in:
@@ -0,0 +1,131 @@
|
||||
package tui
|
||||
|
||||
import "github.com/lixenwraith/terminal"
|
||||
|
||||
// SparklineChars provides 8-level vertical resolution
|
||||
var SparklineChars = []rune{'▁', '▂', '▃', '▄', '▅', '▆', '▇', '█'}
|
||||
|
||||
// SparklineOpts configures sparkline rendering
|
||||
type SparklineOpts struct {
|
||||
Min, Max float64 // Range bounds, auto-scale if both 0
|
||||
Style Style
|
||||
}
|
||||
|
||||
// Sparkline renders an inline graph of values, values are mapped to 8-level block characters
|
||||
func (r Region) Sparkline(x, y, width int, values []float64, opts SparklineOpts) {
|
||||
if y < 0 || y >= r.H || width <= 0 || len(values) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// Determine range
|
||||
min, max := opts.Min, opts.Max
|
||||
if min == 0 && max == 0 {
|
||||
min, max = values[0], values[0]
|
||||
for _, v := range values {
|
||||
if v < min {
|
||||
min = v
|
||||
}
|
||||
if v > max {
|
||||
max = v
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle flat line
|
||||
rangeV := max - min
|
||||
if rangeV == 0 {
|
||||
rangeV = 1
|
||||
}
|
||||
|
||||
// Sample or use last N values if more than width
|
||||
var sampled []float64
|
||||
if len(values) <= width {
|
||||
sampled = values
|
||||
} else {
|
||||
sampled = values[len(values)-width:]
|
||||
}
|
||||
|
||||
// Render each value
|
||||
for i, v := range sampled {
|
||||
if x+i >= r.W {
|
||||
break
|
||||
}
|
||||
|
||||
// Normalize to 0-1
|
||||
norm := (v - min) / rangeV
|
||||
if norm < 0 {
|
||||
norm = 0
|
||||
}
|
||||
if norm > 1 {
|
||||
norm = 1
|
||||
}
|
||||
|
||||
// Map to character index (0-7)
|
||||
idx := int(norm * 7.99)
|
||||
if idx > 7 {
|
||||
idx = 7
|
||||
}
|
||||
|
||||
r.Cell(x+i, y, SparklineChars[idx], opts.Style.Fg, opts.Style.Bg, opts.Style.Attr)
|
||||
}
|
||||
|
||||
// Pad remaining width with lowest char if values shorter than width
|
||||
for i := len(sampled); i < width && x+i < r.W; i++ {
|
||||
r.Cell(x+i, y, SparklineChars[0], opts.Style.Fg, opts.Style.Bg, terminal.AttrDim)
|
||||
}
|
||||
}
|
||||
|
||||
// SparklineV renders vertical sparkline (bottom to top)
|
||||
func (r Region) SparklineV(x, y, height int, values []float64, opts SparklineOpts) {
|
||||
if x < 0 || x >= r.W || height <= 0 || len(values) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
min, max := opts.Min, opts.Max
|
||||
if min == 0 && max == 0 {
|
||||
min, max = values[0], values[0]
|
||||
for _, v := range values {
|
||||
if v < min {
|
||||
min = v
|
||||
}
|
||||
if v > max {
|
||||
max = v
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rangeV := max - min
|
||||
if rangeV == 0 {
|
||||
rangeV = 1
|
||||
}
|
||||
|
||||
var sampled []float64
|
||||
if len(values) <= height {
|
||||
sampled = values
|
||||
} else {
|
||||
sampled = values[len(values)-height:]
|
||||
}
|
||||
|
||||
// Render bottom-up
|
||||
for i, v := range sampled {
|
||||
yPos := y + height - 1 - i
|
||||
if yPos < y || yPos >= r.H {
|
||||
continue
|
||||
}
|
||||
|
||||
norm := (v - min) / rangeV
|
||||
if norm < 0 {
|
||||
norm = 0
|
||||
}
|
||||
if norm > 1 {
|
||||
norm = 1
|
||||
}
|
||||
|
||||
idx := int(norm * 7.99)
|
||||
if idx > 7 {
|
||||
idx = 7
|
||||
}
|
||||
|
||||
r.Cell(x, yPos, SparklineChars[idx], opts.Style.Fg, opts.Style.Bg, opts.Style.Attr)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user