137 lines
2.7 KiB
Go
137 lines
2.7 KiB
Go
|
package formatter
|
||
|
|
||
|
import (
|
||
|
"fmt"
|
||
|
"math"
|
||
|
"strconv"
|
||
|
"strings"
|
||
|
|
||
|
"github.com/samber/lo"
|
||
|
)
|
||
|
|
||
|
type IntervalsInSecondsType map[Interval]int
|
||
|
|
||
|
type Interval string
|
||
|
|
||
|
const (
|
||
|
Year Interval = "year"
|
||
|
Month Interval = "month"
|
||
|
Week Interval = "week"
|
||
|
Day Interval = "day"
|
||
|
Hour Interval = "hour"
|
||
|
Minute Interval = "minute"
|
||
|
Second Interval = "second"
|
||
|
Millisecond Interval = "millisecond"
|
||
|
)
|
||
|
|
||
|
var Units = []Interval{
|
||
|
Year,
|
||
|
Month,
|
||
|
Week,
|
||
|
Day,
|
||
|
Hour,
|
||
|
Minute,
|
||
|
Second,
|
||
|
Millisecond,
|
||
|
}
|
||
|
|
||
|
var IntervalsInSeconds = IntervalsInSecondsType{
|
||
|
Year: 31536000,
|
||
|
Month: 2592000,
|
||
|
Week: 604800,
|
||
|
Day: 86400,
|
||
|
Hour: 3600,
|
||
|
Minute: 60,
|
||
|
Second: 1,
|
||
|
Millisecond: 1,
|
||
|
}
|
||
|
|
||
|
type DecimalCount *int
|
||
|
|
||
|
func toFixed(value float64, decimals DecimalCount) string {
|
||
|
if value == 0 {
|
||
|
return strconv.FormatFloat(value, 'f', getDecimalsForValue(value), 64)
|
||
|
}
|
||
|
|
||
|
if math.IsInf(value, 0) {
|
||
|
return strconv.FormatFloat(value, 'f', -1, 64)
|
||
|
}
|
||
|
|
||
|
if decimals == nil {
|
||
|
count := getDecimalsForValue(value)
|
||
|
decimals = &count
|
||
|
}
|
||
|
|
||
|
factor := math.Pow(10, math.Max(0, float64(*decimals)))
|
||
|
formatted := strconv.FormatFloat(math.Round(value*factor)/factor, 'f', -1, 64)
|
||
|
|
||
|
if formatted == "NaN" {
|
||
|
return ""
|
||
|
}
|
||
|
|
||
|
if formatted == "-0" {
|
||
|
formatted = "0"
|
||
|
}
|
||
|
|
||
|
if value == 0 || strings.Contains(formatted, "e") {
|
||
|
return formatted
|
||
|
}
|
||
|
|
||
|
decimalPos := strings.Index(formatted, ".")
|
||
|
precision := 0
|
||
|
if decimalPos != -1 {
|
||
|
precision = len(formatted) - decimalPos - 1
|
||
|
if precision < *decimals {
|
||
|
return formatted + strings.Repeat("0", *decimals-precision)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return formatted
|
||
|
}
|
||
|
|
||
|
func toFixedScaled(value float64, decimals DecimalCount, scaleFormat string) string {
|
||
|
return toFixed(value, decimals) + scaleFormat
|
||
|
}
|
||
|
|
||
|
func getDecimalsForValue(value float64) int {
|
||
|
absValue := math.Abs(value)
|
||
|
log10 := math.Floor(math.Log10(absValue))
|
||
|
dec := int(-log10 + 1)
|
||
|
magn := math.Pow10(-dec)
|
||
|
norm := absValue / magn
|
||
|
|
||
|
if norm > 2.25 {
|
||
|
dec++
|
||
|
}
|
||
|
|
||
|
if math.Mod(value, 1) == 0 {
|
||
|
dec = 0
|
||
|
}
|
||
|
|
||
|
return int(math.Max(float64(dec), 0))
|
||
|
}
|
||
|
|
||
|
type ValueFormatter func(value float64, decimals *int) string
|
||
|
|
||
|
func logb(base, x float64) float64 {
|
||
|
return math.Log10(x) / math.Log10(base)
|
||
|
}
|
||
|
|
||
|
func scaledUnits(factor float64, extArray []string, offset int) ValueFormatter {
|
||
|
return func(value float64, decimals *int) string {
|
||
|
if value == 0 || math.IsNaN(value) || math.IsInf(value, 0) {
|
||
|
return fmt.Sprintf("%f", value)
|
||
|
}
|
||
|
|
||
|
siIndex := int(math.Floor(logb(factor, math.Abs(value))))
|
||
|
if value < 0 {
|
||
|
siIndex = -siIndex
|
||
|
}
|
||
|
siIndex = lo.Clamp(siIndex+offset, 0, len(extArray)-1)
|
||
|
|
||
|
suffix := extArray[siIndex]
|
||
|
|
||
|
return toFixed(value/math.Pow(factor, float64(siIndex-offset)), decimals) + suffix
|
||
|
}
|
||
|
}
|