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

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

// 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
}