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 }