refactor: support pie chart

This commit is contained in:
vicanso 2021-12-15 23:41:27 +08:00
parent be19cd728a
commit 4d8086a283
5 changed files with 195 additions and 48 deletions

30
axis.go
View file

@ -37,6 +37,7 @@ type (
// 'log' 对数轴。适用于对数数据。 // 'log' 对数轴。适用于对数数据。
Type string Type string
Data []string Data []string
SplitNumber int
} }
) )
@ -44,6 +45,7 @@ const axisStrokeWidth = 1
func GetXAxisAndValues(xAxis XAxis, tickPosition chart.TickPosition, theme string) (chart.XAxis, []float64) { func GetXAxisAndValues(xAxis XAxis, tickPosition chart.TickPosition, theme string) (chart.XAxis, []float64) {
data := xAxis.Data data := xAxis.Data
originalSize := len(data)
// 如果居中,则需要多添加一个值 // 如果居中,则需要多添加一个值
if tickPosition == chart.TickPositionBetweenTicks { if tickPosition == chart.TickPositionBetweenTicks {
data = append([]string{ data = append([]string{
@ -51,14 +53,36 @@ func GetXAxisAndValues(xAxis XAxis, tickPosition chart.TickPosition, theme strin
}, data...) }, data...)
} }
xValues := make([]float64, len(data)) size := len(data)
ticks := make([]chart.Tick, len(data))
xValues := make([]float64, size)
ticks := make([]chart.Tick, 0)
maxTicks := xAxis.SplitNumber
if maxTicks == 0 {
maxTicks = 10
}
// 计息最多每个unit至少放多个
minUnitSize := originalSize / maxTicks
if originalSize%maxTicks != 0 {
minUnitSize++
}
unitSize := minUnitSize
for i := minUnitSize; i < 2*minUnitSize; i++ {
if originalSize%i == 0 {
unitSize = i
}
}
for index, key := range data { for index, key := range data {
f := float64(index) f := float64(index)
xValues[index] = f xValues[index] = f
ticks[index] = chart.Tick{ if index%unitSize == 0 || index == size-1 {
ticks = append(ticks, chart.Tick{
Value: f, Value: f,
Label: key, Label: key,
})
} }
} }
// TODO // TODO

View file

@ -28,6 +28,12 @@ import (
const defaultBarMargin = 10 const defaultBarMargin = 10
type BarSeriesCustomStyle struct {
PointIndex int
Index int
Style chart.Style
}
type BarSeries struct { type BarSeries struct {
BaseSeries BaseSeries
Count int Count int
@ -38,6 +44,16 @@ type BarSeries struct {
Offset int Offset int
// 宽度 // 宽度
BarWidth int BarWidth int
CustomStyles []BarSeriesCustomStyle
}
func (bs BarSeries) GetBarStyle(index, pointIndex int) chart.Style {
for _, item := range bs.CustomStyles {
if item.Index == index && item.PointIndex == pointIndex {
return item.Style
}
}
return chart.Style{}
} }
func (bs BarSeries) Render(r chart.Renderer, canvasBox chart.Box, xrange, yrange chart.Range, defaults chart.Style) { func (bs BarSeries) Render(r chart.Renderer, canvasBox chart.Box, xrange, yrange chart.Range, defaults chart.Style) {
@ -79,6 +95,12 @@ func (bs BarSeries) Render(r chart.Renderer, canvasBox chart.Box, xrange, yrange
for i := 0; i < bs.Len(); i++ { for i := 0; i < bs.Len(); i++ {
vx, vy := bs.GetValues(i) vx, vy := bs.GetValues(i)
customStyle := bs.GetBarStyle(bs.Index, i)
cloneStyle := style
if !customStyle.IsZero() {
cloneStyle.FillColor = customStyle.FillColor
cloneStyle.StrokeColor = customStyle.StrokeColor
}
x := cl + xrange.Translate(vx) x := cl + xrange.Translate(vx)
// 由于bar是居中展示因此需要往前移一个显示块 // 由于bar是居中展示因此需要往前移一个显示块
@ -92,6 +114,6 @@ func (bs BarSeries) Render(r chart.Renderer, canvasBox chart.Box, xrange, yrange
Top: y, Top: y,
Right: x + barWidth, Right: x + barWidth,
Bottom: canvasBox.Bottom - 1, Bottom: canvasBox.Bottom - 1,
}, style) }, cloneStyle)
} }
} }

View file

@ -24,10 +24,17 @@ package charts
import ( import (
"bytes" "bytes"
"errors"
"io"
"github.com/wcharczuk/go-chart/v2" "github.com/wcharczuk/go-chart/v2"
) )
const (
ThemeLight = "light"
ThemeDark = "dark"
)
type ( type (
Title struct { Title struct {
Text string Text string
@ -46,7 +53,13 @@ type (
Legend Legend Legend Legend
TickPosition chart.TickPosition TickPosition chart.TickPosition
} }
ECharOption struct { )
type Chart interface {
Render(rp chart.RendererProvider, w io.Writer) error
}
type ECharOption struct {
Title struct { Title struct {
Text string Text string
TextStyle struct { TextStyle struct {
@ -56,18 +69,27 @@ type (
} }
XAxis struct { XAxis struct {
Type string Type string
BoundaryGap bool BoundaryGap *bool
SplitNumber int
Data []string Data []string
} }
} }
)
const ( func (o *Option) validate() error {
ThemeLight = "light" xAxisCount := len(o.XAxis.Data)
ThemeDark = "dark" if len(o.Series) == 0 {
) return errors.New("series can not be empty")
}
func render(c *chart.Chart, rp chart.RendererProvider) ([]byte, error) { for _, item := range o.Series {
if len(item.Data) != xAxisCount {
return errors.New("series and xAxis is not matched")
}
}
return nil
}
func render(c Chart, rp chart.RendererProvider) ([]byte, error) {
buf := bytes.Buffer{} buf := bytes.Buffer{}
err := c.Render(rp, &buf) err := c.Render(rp, &buf)
if err != nil { if err != nil {
@ -76,14 +98,18 @@ func render(c *chart.Chart, rp chart.RendererProvider) ([]byte, error) {
return buf.Bytes(), nil return buf.Bytes(), nil
} }
func ToPNG(c *chart.Chart) ([]byte, error) { func ToPNG(c Chart) ([]byte, error) {
return render(c, chart.PNG) return render(c, chart.PNG)
} }
func ToSVG(c *chart.Chart) ([]byte, error) { func ToSVG(c Chart) ([]byte, error) {
return render(c, chart.SVG) return render(c, chart.SVG)
} }
func New(opt Option) *chart.Chart { func New(opt Option) (Chart, error) {
err := opt.validate()
if err != nil {
return nil, err
}
tickPosition := opt.TickPosition tickPosition := opt.TickPosition
xAxis, xValues := GetXAxisAndValues(opt.XAxis, tickPosition, opt.Theme) xAxis, xValues := GetXAxisAndValues(opt.XAxis, tickPosition, opt.Theme)
@ -97,6 +123,25 @@ func New(opt Option) *chart.Chart {
opt.Series[index].Name = opt.Legend.Data[index] opt.Series[index].Name = opt.Legend.Data[index]
} }
} }
if opt.Series[0].Type == SeriesPie {
values := make(chart.Values, len(opt.Series))
for index, item := range opt.Series {
values[index] = chart.Value{
Value: item.Data[0].Value,
Label: item.Name,
}
}
c := &chart.PieChart{
Title: opt.Title.Text,
Width: opt.Width,
Height: opt.Height,
Values: values,
ColorPalette: &ThemeColorPalette{
Theme: opt.Theme,
},
}
return c, nil
}
c := &chart.Chart{ c := &chart.Chart{
Title: opt.Title.Text, Title: opt.Title.Text,
@ -106,7 +151,7 @@ func New(opt Option) *chart.Chart {
YAxis: GetYAxis(opt.Theme), YAxis: GetYAxis(opt.Theme),
Series: GetSeries(opt.Series, tickPosition, opt.Theme), Series: GetSeries(opt.Series, tickPosition, opt.Theme),
} }
// TODO 校验xAxis与yAxis的数量是否一致
// 设置secondary的样式 // 设置secondary的样式
c.YAxisSecondary.Style = c.YAxis.Style c.YAxisSecondary.Style = c.YAxis.Style
if legendSize != 0 { if legendSize != 0 {
@ -114,5 +159,5 @@ func New(opt Option) *chart.Chart {
DefaultLegend(c), DefaultLegend(c),
} }
} }
return c return c, nil
} }

View file

@ -24,13 +24,16 @@ package charts
import ( import (
"github.com/wcharczuk/go-chart/v2" "github.com/wcharczuk/go-chart/v2"
"github.com/wcharczuk/go-chart/v2/drawing"
) )
type SeriesData struct {
Value float64
Style chart.Style
}
type Series struct { type Series struct {
Type string Type string
Name string Name string
Data []float64 Data []SeriesData
XValues []float64 XValues []float64
} }
@ -40,15 +43,9 @@ const dotWith = 2
const ( const (
SeriesBar = "bar" SeriesBar = "bar"
SeriesLine = "line" SeriesLine = "line"
SeriesPie = "pie"
) )
func getSeriesColor(theme string, index int) drawing.Color {
// TODO
if theme == ThemeDark {
}
return SeriesColorsLight[index%len(SeriesColorsLight)]
}
func GetSeries(series []Series, tickPosition chart.TickPosition, theme string) []chart.Series { func GetSeries(series []Series, tickPosition chart.TickPosition, theme string) []chart.Series {
arr := make([]chart.Series, len(series)) arr := make([]chart.Series, len(series))
barCount := 0 barCount := 0
@ -58,25 +55,41 @@ func GetSeries(series []Series, tickPosition chart.TickPosition, theme string) [
barCount++ barCount++
} }
} }
for index, item := range series { for index, item := range series {
style := chart.Style{ style := chart.Style{
StrokeWidth: lineStrokeWidth, StrokeWidth: lineStrokeWidth,
StrokeColor: getSeriesColor(theme, index), StrokeColor: getSeriesColor(theme, index),
// FillColor: getSeriesColor(theme, index),
// TODO 调整为通过dot with color 生成 // TODO 调整为通过dot with color 生成
DotColor: getSeriesColor(theme, index), DotColor: getSeriesColor(theme, index),
DotWidth: dotWith, DotWidth: dotWith,
} }
// 如果居中,需要多增加一个点 // 如果居中,需要多增加一个点
if tickPosition == chart.TickPositionBetweenTicks { if tickPosition == chart.TickPositionBetweenTicks {
item.Data = append([]float64{ item.Data = append([]SeriesData{
0.0, {
Value: 0.0,
},
}, item.Data...) }, item.Data...)
} }
yValues := make([]float64, len(item.Data))
barCustomStyles := make([]BarSeriesCustomStyle, 0)
for i, item := range item.Data {
yValues[i] = item.Value
if !item.Style.IsZero() {
barCustomStyles = append(barCustomStyles, BarSeriesCustomStyle{
PointIndex: i,
Index: barIndex,
Style: item.Style,
})
}
}
baseSeries := BaseSeries{ baseSeries := BaseSeries{
Name: item.Name, Name: item.Name,
XValues: item.XValues, XValues: item.XValues,
Style: style, Style: style,
YValues: item.Data, YValues: yValues,
TickPosition: tickPosition, TickPosition: tickPosition,
} }
// TODO 判断类型 // TODO 判断类型
@ -86,6 +99,7 @@ func GetSeries(series []Series, tickPosition chart.TickPosition, theme string) [
Count: barCount, Count: barCount,
Index: barIndex, Index: barIndex,
BaseSeries: baseSeries, BaseSeries: baseSeries,
CustomStyles: barCustomStyles,
} }
barIndex++ barIndex++
default: default:

View file

@ -22,7 +22,10 @@
package charts package charts
import "github.com/wcharczuk/go-chart/v2/drawing" import (
"github.com/wcharczuk/go-chart/v2"
"github.com/wcharczuk/go-chart/v2/drawing"
)
var hiddenColor = drawing.Color{R: 110, G: 112, B: 121, A: 0} var hiddenColor = drawing.Color{R: 110, G: 112, B: 121, A: 0}
@ -65,3 +68,42 @@ var SeriesColorsLight = []drawing.Color{
A: 255, A: 255,
}, },
} }
type ThemeColorPalette struct {
Theme string
}
func (tp ThemeColorPalette) BackgroundColor() drawing.Color {
return chart.DefaultBackgroundColor
}
func (tp ThemeColorPalette) BackgroundStrokeColor() drawing.Color {
return chart.DefaultBackgroundStrokeColor
}
func (tp ThemeColorPalette) CanvasColor() drawing.Color {
return chart.DefaultCanvasColor
}
func (tp ThemeColorPalette) CanvasStrokeColor() drawing.Color {
return chart.DefaultCanvasStrokeColor
}
func (tp ThemeColorPalette) AxisStrokeColor() drawing.Color {
return chart.DefaultAxisColor
}
func (tp ThemeColorPalette) TextColor() drawing.Color {
return chart.DefaultTextColor
}
func (tp ThemeColorPalette) GetSeriesColor(index int) drawing.Color {
return getSeriesColor(tp.Theme, index)
}
func getSeriesColor(theme string, index int) drawing.Color {
// TODO
if theme == ThemeDark {
}
return SeriesColorsLight[index%len(SeriesColorsLight)]
}