- TG: /chart 本月分类饼图, /week 近7天消费柱状图 - QQ: 统计/报表 本月文本统计 - 新增 go-chart 依赖生成 PNG 图表 - 新增 GetCategoryStats/GetDailyStats 查询方法
127 lines
2.8 KiB
Go
127 lines
2.8 KiB
Go
package chart
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"math"
|
|
|
|
"xiaji-go/internal/service"
|
|
|
|
"github.com/wcharczuk/go-chart/v2"
|
|
"github.com/wcharczuk/go-chart/v2/drawing"
|
|
)
|
|
|
|
// 分类对应的颜色
|
|
var categoryColors = []drawing.Color{
|
|
{R: 255, G: 99, B: 132, A: 255}, // 红
|
|
{R: 54, G: 162, B: 235, A: 255}, // 蓝
|
|
{R: 255, G: 206, B: 86, A: 255}, // 黄
|
|
{R: 75, G: 192, B: 192, A: 255}, // 青
|
|
{R: 153, G: 102, B: 255, A: 255}, // 紫
|
|
{R: 255, G: 159, B: 64, A: 255}, // 橙
|
|
{R: 46, G: 204, B: 113, A: 255}, // 绿
|
|
{R: 231, G: 76, B: 60, A: 255}, // 深红
|
|
{R: 52, G: 73, B: 94, A: 255}, // 深蓝灰
|
|
{R: 241, G: 196, B: 15, A: 255}, // 金
|
|
}
|
|
|
|
// GeneratePieChart 生成分类占比饼图
|
|
func GeneratePieChart(stats []service.CategoryStat, title string) ([]byte, error) {
|
|
if len(stats) == 0 {
|
|
return nil, fmt.Errorf("no data")
|
|
}
|
|
|
|
var total float64
|
|
for _, s := range stats {
|
|
total += float64(s.Total)
|
|
}
|
|
|
|
var values []chart.Value
|
|
for i, s := range stats {
|
|
yuan := float64(s.Total) / 100.0
|
|
pct := float64(s.Total) / total * 100
|
|
label := fmt.Sprintf("%s %.0f元(%.0f%%)", s.Category, yuan, pct)
|
|
values = append(values, chart.Value{
|
|
Value: yuan,
|
|
Label: label,
|
|
Style: chart.Style{
|
|
FillColor: categoryColors[i%len(categoryColors)],
|
|
StrokeColor: drawing.ColorWhite,
|
|
StrokeWidth: 2,
|
|
},
|
|
})
|
|
}
|
|
|
|
pie := chart.PieChart{
|
|
Title: title,
|
|
Width: 600,
|
|
Height: 500,
|
|
TitleStyle: chart.Style{
|
|
FontSize: 16,
|
|
},
|
|
Values: values,
|
|
}
|
|
|
|
buf := &bytes.Buffer{}
|
|
if err := pie.Render(chart.PNG, buf); err != nil {
|
|
return nil, fmt.Errorf("render pie chart: %w", err)
|
|
}
|
|
return buf.Bytes(), nil
|
|
}
|
|
|
|
// GenerateBarChart 生成每日消费柱状图
|
|
func GenerateBarChart(stats []service.DailyStat, title string) ([]byte, error) {
|
|
if len(stats) == 0 {
|
|
return nil, fmt.Errorf("no data")
|
|
}
|
|
|
|
var values []chart.Value
|
|
var maxVal float64
|
|
for _, s := range stats {
|
|
yuan := float64(s.Total) / 100.0
|
|
if yuan > maxVal {
|
|
maxVal = yuan
|
|
}
|
|
// 日期只取 MM-DD
|
|
dateLabel := s.Date
|
|
if len(s.Date) > 5 {
|
|
dateLabel = s.Date[5:]
|
|
}
|
|
values = append(values, chart.Value{
|
|
Value: yuan,
|
|
Label: dateLabel,
|
|
Style: chart.Style{
|
|
FillColor: drawing.Color{R: 54, G: 162, B: 235, A: 255},
|
|
StrokeColor: drawing.Color{R: 54, G: 162, B: 235, A: 255},
|
|
StrokeWidth: 1,
|
|
},
|
|
})
|
|
}
|
|
|
|
bar := chart.BarChart{
|
|
Title: title,
|
|
Width: 600,
|
|
Height: 400,
|
|
TitleStyle: chart.Style{
|
|
FontSize: 16,
|
|
},
|
|
YAxis: chart.YAxis{
|
|
Range: &chart.ContinuousRange{
|
|
Min: 0,
|
|
Max: math.Ceil(maxVal*1.2/10) * 10,
|
|
},
|
|
ValueFormatter: func(v interface{}) string {
|
|
return fmt.Sprintf("%.0f", v)
|
|
},
|
|
},
|
|
BarWidth: 40,
|
|
Bars: values,
|
|
}
|
|
|
|
buf := &bytes.Buffer{}
|
|
if err := bar.Render(chart.PNG, buf); err != nil {
|
|
return nil, fmt.Errorf("render bar chart: %w", err)
|
|
}
|
|
return buf.Bytes(), nil
|
|
}
|