You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
naza/pkg/chartbar/ctx.go

226 lines
5.5 KiB
Go

// Copyright 2021, Chef. All rights reserved.
// https://github.com/q191201771/naza
//
// Use of this source code is governed by a MIT-style license
// that can be found in the License file.
//
// Author: Chef (191201771@qq.com)
package chartbar
import (
"encoding/csv"
"fmt"
"math"
"os"
"sort"
"strconv"
"strings"
"github.com/q191201771/naza/pkg/dataops"
)
type Ctx struct {
option Option
}
// WithItems
//
// @param items: 注意,内部不会修改切片底层数据的值以及顺序
func (ctx *Ctx) WithItems(items []Item) string {
// 拷贝一份,避免修改外部切片的原始顺序
if ctx.option.Order != OrderOrigin {
clone := make([]Item, len(items))
copy(clone, items)
items = clone
}
// 排序
switch ctx.option.Order {
case OrderOrigin:
// noop
case OrderAscCount:
sort.Slice(items, func(i, j int) bool {
return items[i].Num < items[j].Num
})
case OrderDescCount:
sort.Slice(items, func(i, j int) bool {
return items[i].Num > items[j].Num
})
case OrderAscName:
sort.Slice(items, func(i, j int) bool {
return items[i].Name < items[j].Name
})
case OrderDescName:
sort.Slice(items, func(i, j int) bool {
return items[i].Name > items[j].Name
})
}
// 选取需要的元素
var newItems []Item
dataops.SliceLimit(items, ctx.option.PrefixNumLimit, ctx.option.SuffixNumLimit, func(index int) {
newItems = append(newItems, items[index])
})
items = newItems
var (
maxLengthOfCount int // count柱状最长画多长
maxLengthOfNum int // num字段多长
)
minN, maxN := dataops.SliceMinMax(items, func(i, j int) bool {
return items[i].Num < items[j].Num
})
minNum := minN.(Item).Num
maxNum := maxN.(Item).Num
isAllInteger := dataops.SliceAllOf(items, func(originItem interface{}) bool {
return isInteger(originItem.(Item).Num)
})
if isAllInteger && (int(maxNum-minNum) < ctx.option.MaxBarLength) {
// 如果都是整数,且实际最大值最小值的差值小于柱状最大长度限制
for i := range items {
if minNum >= 0.00 {
// 都是正整数,按原始值绘制
items[i].count = int(items[i].Num)
} else {
// 最小的负值画1
items[i].count = int(items[i].Num - minNum + 1)
}
}
} else {
for i := range items {
if minNum > 0.00 {
// 都是正数的情况,最大的画满柱状条,其他的按与最大占比画
// round四舍五入
items[i].count = int(math.Round(items[i].Num * float64(ctx.option.MaxBarLength) / maxNum))
} else {
// 有负数的情况最小的负数画1最大的画满
items[i].count = int(math.Round((items[i].Num - minNum) * float64(ctx.option.MaxBarLength) / (maxNum - minNum)))
}
// 最小可能和最大的比太小了
if items[i].count == 0 {
items[i].count = 1
}
}
}
maxLengthOfCount = dataops.SliceMax(items, func(i, j int) bool {
return items[i].count < items[j].count
}).(Item).count
maxn := len(fmt.Sprintf("%0.2f", maxNum))
minn := len(fmt.Sprintf("%0.2f", minNum))
if maxn > minn {
maxLengthOfNum = maxn
} else {
maxLengthOfNum = minn
}
maxLengthOfName := len(dataops.SliceMax(items, func(i, j int) bool {
return len(items[i].Name) < len(items[j].Name)
}).(Item).Name)
var tmpl string
var tmplNum string
if isAllInteger {
// -3是因为整数不需要小数点和小数点的后两位
tmplNum = fmt.Sprintf("%%%d.0f", maxLengthOfNum-3)
} else {
tmplNum = fmt.Sprintf("%%%d.2f", maxLengthOfNum)
}
if !ctx.option.HideNum && !ctx.option.HideName {
tmpl = fmt.Sprintf("%s | %%s%%s | %%-%ds\n", tmplNum, maxLengthOfName)
} else if !ctx.option.HideNum && ctx.option.HideName {
tmpl = fmt.Sprintf("%s | %%s%%s\n", tmplNum)
} else if ctx.option.HideNum && !ctx.option.HideName {
tmpl = fmt.Sprintf("%%s%%s | %%-%ds\n", maxLengthOfName)
} else {
tmpl = "%s\n"
}
var out string
for _, item := range items {
bar := strings.Repeat(ctx.option.DrawIconBlock, item.count)
padding := strings.Repeat(ctx.option.DrawIconPadding, maxLengthOfCount-item.count)
if !ctx.option.HideNum && !ctx.option.HideName {
out += fmt.Sprintf(tmpl, item.Num, bar, padding, item.Name)
} else if !ctx.option.HideNum && ctx.option.HideName {
out += fmt.Sprintf(tmpl, item.Num, bar, padding)
} else if ctx.option.HideNum && !ctx.option.HideName {
out += fmt.Sprintf(tmpl, bar, padding, item.Name)
} else {
out += fmt.Sprintf(tmpl, bar)
}
}
return out
}
func (ctx *Ctx) WithAnySlice(a interface{}, iterateTransFn func(originItem interface{}) Item, modOptions ...ModOption) string {
var items []Item
dataops.IterateInterfaceAsSlice(a, func(iterItem interface{}) bool {
items = append(items, iterateTransFn(iterItem))
return true
})
return ctx.WithItems(items)
}
func (ctx *Ctx) WithMap(m map[string]int) string {
var items []Item
for k, v := range m {
item := Item{
Name: k,
Num: float64(v),
}
items = append(items, item)
}
return ctx.WithItems(items)
}
func (ctx *Ctx) WithMapFloat(m map[string]float64) string {
var items []Item
for k, v := range m {
item := Item{
Name: k,
Num: v,
}
items = append(items, item)
}
return ctx.WithItems(items)
}
func (ctx *Ctx) WithCsv(filename string) (string, error) {
// 读取
fp, err := os.Open(filename)
if err != nil {
return "", err
}
defer fp.Close()
r := csv.NewReader(fp)
records, err := r.ReadAll()
if err != nil {
return "", err
}
var items []Item
for _, line := range records {
var item Item
item.Name = line[0]
item.Num, err = strconv.ParseFloat(line[1], 64)
if err != nil {
return "", err
}
items = append(items, item)
}
return ctx.WithItems(items), nil
}