mirror of
https://github.com/johnkerl/miller.git
synced 2026-07-17 16:38:54 +00:00
Adds a sparkline(array|map) built-in that renders numeric values as a Unicode block-character string (▁▂▃▄▅▆▇█), plus a new `mlr sparkline` verb that summarizes each field's values in record order -- useful for eyeballing trends without external plotting tools. Also adds `-s` to `mlr histogram` to sparkline a field's binned counts (its distribution shape) rather than emitting one record per bin. This is a different chart from `mlr sparkline` (order-independent binning vs. record-order values), and the docs for each cross-reference the other to avoid conflating them. Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
72 lines
1.8 KiB
Go
72 lines
1.8 KiB
Go
package bifs
|
|
|
|
import (
|
|
"github.com/johnkerl/miller/v6/pkg/mlrval"
|
|
)
|
|
|
|
// sparklineTicks are the eighth-height Unicode block characters used to
|
|
// render array/map values as a compact one-line bar chart.
|
|
var sparklineTicks = []rune("▁▂▃▄▅▆▇█")
|
|
|
|
func BIF_sparkline(collection *mlrval.Mlrval) *mlrval.Mlrval {
|
|
ok, valueIfNot := check_collection(collection, "sparkline")
|
|
if !ok {
|
|
return valueIfNot
|
|
}
|
|
|
|
var floatValues []float64
|
|
if collection.IsArray() {
|
|
array := collection.AcquireArrayValue()
|
|
floatValues = make([]float64, 0, len(array))
|
|
for _, element := range array {
|
|
floatValue, isFloat := element.GetNumericToFloatValue()
|
|
if !isFloat {
|
|
return mlrval.FromNotNumericError("sparkline", element)
|
|
}
|
|
floatValues = append(floatValues, floatValue)
|
|
}
|
|
} else {
|
|
m := collection.AcquireMapValue()
|
|
floatValues = make([]float64, 0, m.FieldCount)
|
|
for pe := m.Head; pe != nil; pe = pe.Next {
|
|
floatValue, isFloat := pe.Value.GetNumericToFloatValue()
|
|
if !isFloat {
|
|
return mlrval.FromNotNumericError("sparkline", pe.Value)
|
|
}
|
|
floatValues = append(floatValues, floatValue)
|
|
}
|
|
}
|
|
|
|
if len(floatValues) == 0 {
|
|
return mlrval.VOID
|
|
}
|
|
|
|
lo := floatValues[0]
|
|
hi := floatValues[0]
|
|
for _, floatValue := range floatValues[1:] {
|
|
if floatValue < lo {
|
|
lo = floatValue
|
|
}
|
|
if floatValue > hi {
|
|
hi = floatValue
|
|
}
|
|
}
|
|
|
|
numTicks := len(sparklineTicks)
|
|
runes := make([]rune, len(floatValues))
|
|
for i, floatValue := range floatValues {
|
|
if hi == lo {
|
|
runes[i] = sparklineTicks[0]
|
|
continue
|
|
}
|
|
tickIndex := int(float64(numTicks-1)*(floatValue-lo)/(hi-lo) + 0.5)
|
|
if tickIndex < 0 {
|
|
tickIndex = 0
|
|
} else if tickIndex >= numTicks {
|
|
tickIndex = numTicks - 1
|
|
}
|
|
runes[i] = sparklineTicks[tickIndex]
|
|
}
|
|
|
|
return mlrval.FromString(string(runes))
|
|
}
|