refactor: support pie chart
This commit is contained in:
parent
be19cd728a
commit
4d8086a283
5 changed files with 195 additions and 48 deletions
38
axis.go
38
axis.go
|
|
@ -35,8 +35,9 @@ type (
|
||||||
// 'category' 类目轴,适用于离散的类目数据。为该类型时类目数据可自动从 series.data 或 dataset.source 中取,或者可通过 xAxis.data 设置类目数据。
|
// 'category' 类目轴,适用于离散的类目数据。为该类型时类目数据可自动从 series.data 或 dataset.source 中取,或者可通过 xAxis.data 设置类目数据。
|
||||||
// 'time' 时间轴,适用于连续的时序数据,与数值轴相比时间轴带有时间的格式化,在刻度计算上也有所不同,例如会根据跨度的范围来决定使用月,星期,日还是小时范围的刻度。
|
// 'time' 时间轴,适用于连续的时序数据,与数值轴相比时间轴带有时间的格式化,在刻度计算上也有所不同,例如会根据跨度的范围来决定使用月,星期,日还是小时范围的刻度。
|
||||||
// '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 {
|
||||||
Value: f,
|
ticks = append(ticks, chart.Tick{
|
||||||
Label: key,
|
Value: f,
|
||||||
|
Label: key,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// TODO
|
// TODO
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
@ -37,7 +43,17 @@ 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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
91
charts.go
91
charts.go
|
|
@ -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,28 +53,43 @@ type (
|
||||||
Legend Legend
|
Legend Legend
|
||||||
TickPosition chart.TickPosition
|
TickPosition chart.TickPosition
|
||||||
}
|
}
|
||||||
ECharOption struct {
|
)
|
||||||
Title struct {
|
|
||||||
Text string
|
type Chart interface {
|
||||||
TextStyle struct {
|
Render(rp chart.RendererProvider, w io.Writer) error
|
||||||
Color string
|
}
|
||||||
FontFamily string
|
|
||||||
}
|
type ECharOption struct {
|
||||||
}
|
Title struct {
|
||||||
XAxis struct {
|
Text string
|
||||||
Type string
|
TextStyle struct {
|
||||||
BoundaryGap bool
|
Color string
|
||||||
Data []string
|
FontFamily string
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
XAxis struct {
|
||||||
|
Type string
|
||||||
|
BoundaryGap *bool
|
||||||
|
SplitNumber int
|
||||||
|
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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
44
series.go
44
series.go
|
|
@ -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,34 +55,51 @@ 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 判断类型
|
||||||
switch item.Type {
|
switch item.Type {
|
||||||
case SeriesBar:
|
case SeriesBar:
|
||||||
arr[index] = BarSeries{
|
arr[index] = BarSeries{
|
||||||
Count: barCount,
|
Count: barCount,
|
||||||
Index: barIndex,
|
Index: barIndex,
|
||||||
BaseSeries: baseSeries,
|
BaseSeries: baseSeries,
|
||||||
|
CustomStyles: barCustomStyles,
|
||||||
}
|
}
|
||||||
barIndex++
|
barIndex++
|
||||||
default:
|
default:
|
||||||
|
|
|
||||||
44
theme.go
44
theme.go
|
|
@ -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)]
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue