From 2316689ce59ecb25889dd4b9c3d2165354eec962 Mon Sep 17 00:00:00 2001 From: vicanso Date: Tue, 8 Mar 2022 23:33:16 +0800 Subject: [PATCH] feat: support option function for chart render --- chart.go | 5 +- chart_option.go | 185 +++++++++++++++++++++++++++++++++ chart_option_test.go | 238 +++++++++++++++++++++++++++++++++++++++++++ series.go | 2 +- series_test.go | 2 +- 5 files changed, 429 insertions(+), 3 deletions(-) create mode 100644 chart_option.go create mode 100644 chart_option_test.go diff --git a/chart.go b/chart.go index 9748a95..6f4740f 100644 --- a/chart.go +++ b/chart.go @@ -283,7 +283,10 @@ func (r *basicRenderResult) getYRange(index int) *Range { } // Render renders the chart by option -func Render(opt ChartOption) (*Draw, error) { +func Render(opt ChartOption, optFuncs ...OptionFunc) (*Draw, error) { + for _, optFunc := range optFuncs { + optFunc(&opt) + } if len(opt.SeriesList) == 0 { return nil, errors.New("series can not be nil") } diff --git a/chart_option.go b/chart_option.go new file mode 100644 index 0000000..1bbf4cf --- /dev/null +++ b/chart_option.go @@ -0,0 +1,185 @@ +// MIT License + +// Copyright (c) 2022 Tree Xie + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +package charts + +import ( + "github.com/wcharczuk/go-chart/v2" + "github.com/wcharczuk/go-chart/v2/drawing" +) + +// OptionFunc option function +type OptionFunc func(opt *ChartOption) + +// TypeOptionFunc set type of chart's output +func TypeOptionFunc(t string) OptionFunc { + return func(opt *ChartOption) { + opt.Type = t + } +} + +// FontFamilyOptionFunc set font family of chart +func FontFamilyOptionFunc(fontFamily string) OptionFunc { + return func(opt *ChartOption) { + opt.FontFamily = fontFamily + } +} + +// ThemeOptionFunc set them of chart +func ThemeOptionFunc(theme string) OptionFunc { + return func(opt *ChartOption) { + opt.Theme = theme + } +} + +// TitleOptionFunc set title of chart +func TitleOptionFunc(title TitleOption) OptionFunc { + return func(opt *ChartOption) { + opt.Title = title + } +} + +// LegendOptionFunc set legend of chart +func LegendOptionFunc(legend LegendOption) OptionFunc { + return func(opt *ChartOption) { + opt.Legend = legend + } +} + +// XAxisOptionFunc set x axis of chart +func XAxisOptionFunc(xAxisOption XAxisOption) OptionFunc { + return func(opt *ChartOption) { + opt.XAxis = xAxisOption + } +} + +// YAxisOptionFunc set y axis of chart, support two y axis +func YAxisOptionFunc(yAxisOption ...YAxisOption) OptionFunc { + return func(opt *ChartOption) { + opt.YAxisList = yAxisOption + } +} + +// WidthOptionFunc set width of chart +func WidthOptionFunc(width int) OptionFunc { + return func(opt *ChartOption) { + opt.Width = width + } +} + +// HeightOptionFunc set height of chart +func HeightOptionFunc(height int) OptionFunc { + return func(opt *ChartOption) { + opt.Height = height + } +} + +// PaddingOptionFunc set padding of chart +func PaddingOptionFunc(padding chart.Box) OptionFunc { + return func(opt *ChartOption) { + opt.Padding = padding + } +} + +// BoxOptionFunc set box of chart +func BoxOptionFunc(box chart.Box) OptionFunc { + return func(opt *ChartOption) { + opt.Box = box + } +} + +// ChildOptionFunc add child chart +func ChildOptionFunc(child ...ChartOption) OptionFunc { + return func(opt *ChartOption) { + if opt.Children == nil { + opt.Children = make([]ChartOption, 0) + } + opt.Children = append(opt.Children, child...) + } +} + +// RadarIndicatorOptionFunc set radar indicator of chart +func RadarIndicatorOptionFunc(radarIndicator ...RadarIndicator) OptionFunc { + return func(opt *ChartOption) { + opt.RadarIndicators = radarIndicator + } +} + +// BackgroundColorOptionFunc set background color of chart +func BackgroundColorOptionFunc(color drawing.Color) OptionFunc { + return func(opt *ChartOption) { + opt.BackgroundColor = color + } +} + +// LineRender line chart render +func LineRender(values [][]float64, opts ...OptionFunc) (*Draw, error) { + seriesList := make(SeriesList, len(values)) + for index, value := range values { + seriesList[index] = NewSeriesFromValues(value, ChartTypeLine) + } + return Render(ChartOption{ + SeriesList: seriesList, + }, opts...) +} + +// BarRender bar chart render +func BarRender(values [][]float64, opts ...OptionFunc) (*Draw, error) { + seriesList := make(SeriesList, len(values)) + for index, value := range values { + seriesList[index] = NewSeriesFromValues(value, ChartTypeBar) + } + return Render(ChartOption{ + SeriesList: seriesList, + }, opts...) +} + +// PieRender pie chart render +func PieRender(values []float64, opts ...OptionFunc) (*Draw, error) { + return Render(ChartOption{ + SeriesList: NewPieSeriesList(values), + }, opts...) +} + +// RadarRender radar chart render +func RadarRender(values [][]float64, opts ...OptionFunc) (*Draw, error) { + seriesList := make(SeriesList, len(values)) + for index, value := range values { + seriesList[index] = NewSeriesFromValues(value, ChartTypeRadar) + } + return Render(ChartOption{ + SeriesList: seriesList, + }, opts...) +} + +// FunnelRender funnel chart render +func FunnelRender(values []float64, opts ...OptionFunc) (*Draw, error) { + seriesList := make(SeriesList, len(values)) + for index, value := range values { + seriesList[index] = NewSeriesFromValues([]float64{ + value, + }, ChartTypeFunnel) + } + return Render(ChartOption{ + SeriesList: seriesList, + }, opts...) +} diff --git a/chart_option_test.go b/chart_option_test.go new file mode 100644 index 0000000..41e8d50 --- /dev/null +++ b/chart_option_test.go @@ -0,0 +1,238 @@ +// MIT License + +// Copyright (c) 2022 Tree Xie + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +package charts + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/wcharczuk/go-chart/v2" + "github.com/wcharczuk/go-chart/v2/drawing" +) + +func TestOptionFunc(t *testing.T) { + assert := assert.New(t) + + fns := []OptionFunc{ + TypeOptionFunc(ChartOutputPNG), + FontFamilyOptionFunc("fontFamily"), + ThemeOptionFunc("black"), + TitleOptionFunc(TitleOption{ + Text: "title", + }), + LegendOptionFunc(LegendOption{ + Data: []string{ + "a", + "b", + }, + }), + XAxisOptionFunc(NewXAxisOption([]string{ + "Mon", + "Tue", + })), + YAxisOptionFunc(YAxisOption{ + Min: NewFloatPoint(0), + Max: NewFloatPoint(100), + }), + WidthOptionFunc(400), + HeightOptionFunc(300), + PaddingOptionFunc(chart.Box{ + Top: 10, + }), + BoxOptionFunc(chart.Box{ + Left: 0, + Right: 300, + }), + ChildOptionFunc(ChartOption{}), + RadarIndicatorOptionFunc(RadarIndicator{ + Min: 0, + Max: 10, + }), + BackgroundColorOptionFunc(drawing.ColorBlack), + } + + opt := ChartOption{} + for _, fn := range fns { + fn(&opt) + } + + assert.Equal("png", opt.Type) + assert.Equal("fontFamily", opt.FontFamily) + assert.Equal("black", opt.Theme) + assert.Equal(TitleOption{ + Text: "title", + }, opt.Title) + assert.Equal(LegendOption{ + Data: []string{ + "a", + "b", + }, + }, opt.Legend) + assert.Equal(NewXAxisOption([]string{ + "Mon", + "Tue", + }), opt.XAxis) + assert.Equal([]YAxisOption{ + { + Min: NewFloatPoint(0), + Max: NewFloatPoint(100), + }, + }, opt.YAxisList) + assert.Equal(400, opt.Width) + assert.Equal(300, opt.Height) + assert.Equal(chart.Box{ + Top: 10, + }, opt.Padding) + assert.Equal(chart.Box{ + Left: 0, + Right: 300, + }, opt.Box) + assert.Equal(1, len(opt.Children)) + assert.Equal([]RadarIndicator{ + { + Min: 0, + Max: 10, + }, + }, opt.RadarIndicators) + assert.Equal(drawing.ColorBlack, opt.BackgroundColor) +} + +func TestLineRender(t *testing.T) { + assert := assert.New(t) + + d, err := LineRender([][]float64{ + { + 1, + 2, + 3, + }, + { + 1, + 5, + 2, + }, + }, + XAxisOptionFunc(NewXAxisOption([]string{ + "01", + "02", + "03", + })), + ) + assert.Nil(err) + data, err := d.Bytes() + assert.Nil(err) + assert.Equal("\\n010203024681012", string(data)) +} + +func TestBarRender(t *testing.T) { + assert := assert.New(t) + + d, err := BarRender([][]float64{ + { + 1, + 2, + 3, + }, + { + 1, + 5, + 2, + }, + }, + XAxisOptionFunc(NewXAxisOption([]string{ + "01", + "02", + "03", + })), + ) + assert.Nil(err) + data, err := d.Bytes() + assert.Nil(err) + assert.Equal("\\n010203024681012", string(data)) +} + +func TestPieRender(t *testing.T) { + assert := assert.New(t) + + d, err := PieRender([]float64{ + 1, + 3, + 5, + }) + assert.Nil(err) + data, err := d.Bytes() + assert.Nil(err) + assert.Equal("\\n", string(data)) +} + +func TestRadarRender(t *testing.T) { + assert := assert.New(t) + d, err := RadarRender([][]float64{ + { + 1, + 2, + 3, + }, + { + 1, + 5, + 2, + }, + }, + RadarIndicatorOptionFunc([]RadarIndicator{ + { + Name: "A", + Min: 0, + Max: 10, + }, + { + Name: "B", + Min: 0, + Max: 10, + }, + { + Name: "C", + Min: 0, + Max: 10, + }, + }...), + ) + assert.Nil(err) + data, err := d.Bytes() + assert.Nil(err) + assert.Equal("\\nABC", string(data)) +} + +func TestFunnelRender(t *testing.T) { + assert := assert.New(t) + + d, err := FunnelRender([]float64{ + 5, + 3, + 1, + }) + assert.Nil(err) + data, err := d.Bytes() + assert.Nil(err) + assert.Equal("\\n(100%)(60%)(20%)", string(data)) +} diff --git a/series.go b/series.go index a1b7486..14227d1 100644 --- a/series.go +++ b/series.go @@ -128,7 +128,7 @@ type PieSeriesOption struct { Names []string } -func NewPieSeriesList(values []float64, opts ...PieSeriesOption) []Series { +func NewPieSeriesList(values []float64, opts ...PieSeriesOption) SeriesList { result := make([]Series, len(values)) var opt PieSeriesOption if len(opts) != 0 { diff --git a/series_test.go b/series_test.go index aae83de..1460180 100644 --- a/series_test.go +++ b/series_test.go @@ -66,7 +66,7 @@ func TestNewSeriesDataFromValues(t *testing.T) { func TestNewPieSeriesList(t *testing.T) { assert := assert.New(t) - assert.Equal([]Series{ + assert.Equal(SeriesList{ { Type: ChartTypePie, Name: "a",