diff --git a/chart_option.go b/chart_option.go index db2f57b..94574a1 100644 --- a/chart_option.go +++ b/chart_option.go @@ -69,9 +69,9 @@ type ChartOption struct { // OptionFunc option function type OptionFunc func(opt *ChartOption) -// PNGTypeOption set png type of chart's output -func PNGTypeOption() OptionFunc { - return TypeOptionFunc(ChartOutputPNG) +// SVGTypeOption set svg type of chart's output +func SVGTypeOption() OptionFunc { + return TypeOptionFunc(ChartOutputSVG) } // TypeOptionFunc set type of chart's output diff --git a/chart_option_test.go b/chart_option_test.go new file mode 100644 index 0000000..c77bb4f --- /dev/null +++ b/chart_option_test.go @@ -0,0 +1,451 @@ +// 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/drawing" +) + +func TestChartOption(t *testing.T) { + assert := assert.New(t) + + fns := []OptionFunc{ + SVGTypeOption(), + FontFamilyOptionFunc("fontFamily"), + ThemeOptionFunc("theme"), + TitleTextOptionFunc("title"), + LegendLabelsOptionFunc([]string{ + "label", + }), + XAxisDataOptionFunc([]string{ + "xaxis", + }), + YAxisDataOptionFunc([]string{ + "yaxis", + }), + WidthOptionFunc(800), + HeightOptionFunc(600), + PaddingOptionFunc(Box{ + Left: 10, + Top: 10, + Right: 10, + Bottom: 10, + }), + BackgroundColorOptionFunc(drawing.ColorBlack), + } + opt := ChartOption{} + for _, fn := range fns { + fn(&opt) + } + assert.Equal(ChartOption{ + Type: ChartOutputSVG, + FontFamily: "fontFamily", + Theme: "theme", + Title: TitleOption{ + Text: "title", + }, + Legend: LegendOption{ + Data: []string{ + "label", + }, + }, + XAxis: XAxisOption{ + Data: []string{ + "xaxis", + }, + }, + YAxisOptions: []YAxisOption{ + { + Data: []string{ + "yaxis", + }, + }, + }, + Width: 800, + Height: 600, + Padding: Box{ + Left: 10, + Top: 10, + Right: 10, + Bottom: 10, + }, + BackgroundColor: drawing.ColorBlack, + }, opt) +} + +func TestChartOptionPieSeriesShowLabel(t *testing.T) { + assert := assert.New(t) + + opt := ChartOption{ + SeriesList: NewPieSeriesList([]float64{ + 1, + 2, + }), + } + PieSeriesShowLabel()(&opt) + assert.True(opt.SeriesList[0].Label.Show) +} + +func TestChartOptionMarkLine(t *testing.T) { + assert := assert.New(t) + opt := ChartOption{ + SeriesList: NewSeriesListDataFromValues([][]float64{ + {1, 2}, + }), + } + MarkLineOptionFunc(0, "min", "max")(&opt) + assert.Equal(NewMarkLine("min", "max"), opt.SeriesList[0].MarkLine) +} + +func TestChartOptionMarkPoint(t *testing.T) { + assert := assert.New(t) + opt := ChartOption{ + SeriesList: NewSeriesListDataFromValues([][]float64{ + {1, 2}, + }), + } + MarkPointOptionFunc(0, "min", "max")(&opt) + assert.Equal(NewMarkPoint("min", "max"), opt.SeriesList[0].MarkPoint) +} + +func TestLineRender(t *testing.T) { + assert := assert.New(t) + values := [][]float64{ + { + 120, + 132, + 101, + 134, + 90, + 230, + 210, + }, + { + 220, + 182, + 191, + 234, + 290, + 330, + 310, + }, + { + 150, + 232, + 201, + 154, + 190, + 330, + 410, + }, + { + 320, + 332, + 301, + 334, + 390, + 330, + 320, + }, + { + 820, + 932, + 901, + 934, + 1290, + 1330, + 1320, + }, + } + p, err := LineRender( + values, + SVGTypeOption(), + TitleTextOptionFunc("Line"), + XAxisDataOptionFunc([]string{ + "Mon", + "Tue", + "Wed", + "Thu", + "Fri", + "Sat", + "Sun", + }), + LegendLabelsOptionFunc([]string{ + "Email", + "Union Ads", + "Video Ads", + "Direct", + "Search Engine", + }, PositionCenter), + ) + assert.Nil(err) + data, err := p.Bytes() + assert.Nil(err) + assert.Equal("\\nEmailUnion AdsVideo AdsDirectSearch EngineLine1.44k1.2k9607204802400MonTueWedThuFriSatSun", string(data)) +} + +func TestBarRender(t *testing.T) { + assert := assert.New(t) + values := [][]float64{ + { + 2.0, + 4.9, + 7.0, + 23.2, + 25.6, + 76.7, + 135.6, + 162.2, + 32.6, + 20.0, + 6.4, + 3.3, + }, + { + 2.6, + 5.9, + 9.0, + 26.4, + 28.7, + 70.7, + 175.6, + 182.2, + 48.7, + 18.8, + 6.0, + 2.3, + }, + } + p, err := BarRender( + values, + SVGTypeOption(), + XAxisDataOptionFunc([]string{ + "Jan", + "Feb", + "Mar", + "Apr", + "May", + "Jun", + "Jul", + "Aug", + "Sep", + "Oct", + "Nov", + "Dec", + }), + LegendLabelsOptionFunc([]string{ + "Rainfall", + "Evaporation", + }, PositionRight), + MarkLineOptionFunc(0, SeriesMarkDataTypeAverage), + MarkPointOptionFunc(0, SeriesMarkDataTypeMax, + SeriesMarkDataTypeMin), + // custom option func + func(opt *ChartOption) { + opt.SeriesList[1].MarkPoint = NewMarkPoint( + SeriesMarkDataTypeMax, + SeriesMarkDataTypeMin, + ) + opt.SeriesList[1].MarkLine = NewMarkLine( + SeriesMarkDataTypeAverage, + ) + }, + ) + assert.Nil(err) + data, err := p.Bytes() + assert.Nil(err) + assert.Equal("\\nRainfallEvaporation24020016012080400JanFebMarAprMayJunJulAugSepOctNovDec162.22182.22.341.6248.07", string(data)) +} + +func TestHorizontalBarRender(t *testing.T) { + assert := assert.New(t) + values := [][]float64{ + { + 18203, + 23489, + 29034, + 104970, + 131744, + 630230, + }, + { + 19325, + 23438, + 31000, + 121594, + 134141, + 681807, + }, + } + p, err := HorizontalBarRender( + values, + SVGTypeOption(), + TitleTextOptionFunc("World Population"), + PaddingOptionFunc(Box{ + Top: 20, + Right: 40, + Bottom: 20, + Left: 20, + }), + LegendLabelsOptionFunc([]string{ + "2011", + "2012", + }), + YAxisDataOptionFunc([]string{ + "Brazil", + "Indonesia", + "USA", + "India", + "China", + "World", + }), + ) + assert.Nil(err) + data, err := p.Bytes() + assert.Nil(err) + assert.Equal("\\n20112012World PopulationWorldChinaIndiaUSAIndonesiaBrazil0122.28k244.56k366.84k489.12k611.4k733.68k", string(data)) +} + +func TestPieRender(t *testing.T) { + assert := assert.New(t) + values := []float64{ + 1048, + 735, + 580, + 484, + 300, + } + p, err := PieRender( + values, + SVGTypeOption(), + TitleOptionFunc(TitleOption{ + Text: "Rainfall vs Evaporation", + Subtext: "Fake Data", + Left: PositionCenter, + }), + PaddingOptionFunc(Box{ + Top: 20, + Right: 20, + Bottom: 20, + Left: 20, + }), + LegendOptionFunc(LegendOption{ + Orient: OrientVertical, + Data: []string{ + "Search Engine", + "Direct", + "Email", + "Union Ads", + "Video Ads", + }, + Left: PositionLeft, + }), + PieSeriesShowLabel(), + ) + assert.Nil(err) + data, err := p.Bytes() + assert.Nil(err) + assert.Equal("\\nSearch EngineDirectEmailUnion AdsVideo AdsRainfall vs EvaporationFake DataSearch Engine: 33.3%Direct: 23.35%Email: 18.43%Union Ads: 15.37%Video Ads: 9.53%", string(data)) +} + +func TestRadarRender(t *testing.T) { + assert := assert.New(t) + + values := [][]float64{ + { + 4200, + 3000, + 20000, + 35000, + 50000, + 18000, + }, + { + 5000, + 14000, + 28000, + 26000, + 42000, + 21000, + }, + } + p, err := RadarRender( + values, + SVGTypeOption(), + TitleTextOptionFunc("Basic Radar Chart"), + LegendLabelsOptionFunc([]string{ + "Allocated Budget", + "Actual Spending", + }), + RadarIndicatorOptionFunc([]string{ + "Sales", + "Administration", + "Information Technology", + "Customer Support", + "Development", + "Marketing", + }, []float64{ + 6500, + 16000, + 30000, + 38000, + 52000, + 25000, + }), + ) + assert.Nil(err) + data, err := p.Bytes() + assert.Nil(err) + assert.Equal("\\nAllocated BudgetActual SpendingBasic Radar ChartSalesAdministrationInformation TechnologyCustomer SupportDevelopmentMarketing", string(data)) +} + +func TestFunnelRender(t *testing.T) { + assert := assert.New(t) + + values := []float64{ + 100, + 80, + 60, + 40, + 20, + } + p, err := FunnelRender( + values, + SVGTypeOption(), + TitleTextOptionFunc("Funnel"), + LegendLabelsOptionFunc([]string{ + "Show", + "Click", + "Visit", + "Inquiry", + "Order", + }), + ) + assert.Nil(err) + data, err := p.Bytes() + assert.Nil(err) + assert.Equal("\\nShowClickVisitInquiryOrderFunnelShow(100%)Click(80%)Visit(60%)Inquiry(40%)Order(20%)", string(data)) +} diff --git a/echarts.go b/echarts.go index d2602b3..fbe9a36 100644 --- a/echarts.go +++ b/echarts.go @@ -60,9 +60,9 @@ type EChartStyle struct { Color string `json:"color"` } -func (es *EChartStyle) ToStyle() chart.Style { +func (es *EChartStyle) ToStyle() Style { color := parseColor(es.Color) - return chart.Style{ + return Style{ FillColor: color, FontColor: color, StrokeColor: color, diff --git a/echarts_test.go b/echarts_test.go index 1ed14d3..9c31286 100644 --- a/echarts_test.go +++ b/echarts_test.go @@ -27,6 +27,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/wcharczuk/go-chart/v2/drawing" ) func TestConvertToArray(t *testing.T) { @@ -59,6 +60,7 @@ func TestEChartsSeriesDataValue(t *testing.T) { 2, }, }, es) + assert.Equal(NewEChartsSeriesDataValue(1, 2), es) assert.Equal(1.0, es.First()) } @@ -109,47 +111,380 @@ func TestEChartsXAxis(t *testing.T) { }, ex) } +func TestEChartStyle(t *testing.T) { + assert := assert.New(t) + + es := EChartStyle{ + Color: "#999", + } + color := drawing.Color{ + R: 153, + G: 153, + B: 153, + A: 255, + } + assert.Equal(Style{ + FillColor: color, + FontColor: color, + StrokeColor: color, + }, es.ToStyle()) +} + +func TestEChartsPadding(t *testing.T) { + assert := assert.New(t) + + eb := EChartsPadding{} + + err := eb.UnmarshalJSON([]byte(`1`)) + assert.Nil(err) + assert.Equal(Box{ + Left: 1, + Top: 1, + Right: 1, + Bottom: 1, + }, eb.Box) + + err = eb.UnmarshalJSON([]byte(`[2, 3]`)) + assert.Nil(err) + assert.Equal(Box{ + Left: 3, + Top: 2, + Right: 3, + Bottom: 2, + }, eb.Box) + + err = eb.UnmarshalJSON([]byte(`[4, 5, 6]`)) + assert.Nil(err) + assert.Equal(Box{ + Left: 5, + Top: 4, + Right: 5, + Bottom: 6, + }, eb.Box) + + err = eb.UnmarshalJSON([]byte(`[4, 5, 6, 7]`)) + assert.Nil(err) + assert.Equal(Box{ + Left: 7, + Top: 4, + Right: 5, + Bottom: 6, + }, eb.Box) +} + +func TestEChartsMarkPoint(t *testing.T) { + assert := assert.New(t) + + emp := EChartsMarkPoint{ + SymbolSize: 30, + Data: []EChartsMarkData{ + { + Type: "test", + }, + }, + } + assert.Equal(SeriesMarkPoint{ + SymbolSize: 30, + Data: []SeriesMarkData{ + { + Type: "test", + }, + }, + }, emp.ToSeriesMarkPoint()) +} + +func TestEChartsMarkLine(t *testing.T) { + assert := assert.New(t) + + eml := EChartsMarkLine{ + Data: []EChartsMarkData{ + { + Type: "min", + }, + { + Type: "max", + }, + }, + } + assert.Equal(SeriesMarkLine{ + Data: []SeriesMarkData{ + { + Type: "min", + }, + { + Type: "max", + }, + }, + }, eml.ToSeriesMarkLine()) +} + func TestEChartsOption(t *testing.T) { assert := assert.New(t) - opt := EChartsOption{} - err := json.Unmarshal([]byte(`{ + tests := []struct { + option string + }{ + { + option: `{ + "xAxis": { + "type": "category", + "data": [ + "Mon", + "Tue", + "Wed", + "Thu", + "Fri", + "Sat", + "Sun" + ] + }, + "yAxis": { + "type": "value" + }, + "series": [ + { + "data": [ + 120, + { + "value": 200, + "itemStyle": { + "color": "#a90000" + } + }, + 150, + 80, + 70, + 110, + 130 + ], + "type": "bar" + } + ] + }`, + }, + { + option: `{ + "title": { + "text": "Referer of a Website", + "subtext": "Fake Data", + "left": "center" + }, + "tooltip": { + "trigger": "item" + }, + "legend": { + "orient": "vertical", + "left": "left" + }, + "series": [ + { + "name": "Access From", + "type": "pie", + "radius": "50%", + "data": [ + { + "value": 1048, + "name": "Search Engine" + }, + { + "value": 735, + "name": "Direct" + }, + { + "value": 580, + "name": "Email" + }, + { + "value": 484, + "name": "Union Ads" + }, + { + "value": 300, + "name": "Video Ads" + } + ], + "emphasis": { + "itemStyle": { + "shadowBlur": 10, + "shadowOffsetX": 0, + "shadowColor": "rgba(0, 0, 0, 0.5)" + } + } + } + ] + }`, + }, + { + option: `{ + "title": { + "text": "Rainfall vs Evaporation", + "subtext": "Fake Data" + }, + "tooltip": { + "trigger": "axis" + }, + "legend": { + "data": [ + "Rainfall", + "Evaporation" + ] + }, + "toolbox": { + "show": true, + "feature": { + "dataView": { + "show": true, + "readOnly": false + }, + "magicType": { + "show": true, + "type": [ + "line", + "bar" + ] + }, + "restore": { + "show": true + }, + "saveAsImage": { + "show": true + } + } + }, + "calculable": true, + "xAxis": [ + { + "type": "category", + "data": [ + "Jan", + "Feb", + "Mar", + "Apr", + "May", + "Jun", + "Jul", + "Aug", + "Sep", + "Oct", + "Nov", + "Dec" + ] + } + ], + "yAxis": [ + { + "type": "value" + } + ], + "series": [ + { + "name": "Rainfall", + "type": "bar", + "data": [ + 2, + 4.9, + 7, + 23.2, + 25.6, + 76.7, + 135.6, + 162.2, + 32.6, + 20, + 6.4, + 3.3 + ], + "markPoint": { + "data": [ + { + "type": "max", + "name": "Max" + }, + { + "type": "min", + "name": "Min" + } + ] + }, + "markLine": { + "data": [ + { + "type": "average", + "name": "Avg" + } + ] + } + }, + { + "name": "Evaporation", + "type": "bar", + "data": [ + 2.6, + 5.9, + 9, + 26.4, + 28.7, + 70.7, + 175.6, + 182.2, + 48.7, + 18.8, + 6, + 2.3 + ], + "markPoint": { + "data": [ + { + "name": "Max", + "value": 182.2, + "xAxis": 7, + "yAxis": 183 + }, + { + "name": "Min", + "value": 2.3, + "xAxis": 11, + "yAxis": 3 + } + ] + }, + "markLine": { + "data": [ + { + "type": "average", + "name": "Avg" + } + ] + } + } + ] + }`, + }, + } + for _, tt := range tests { + opt := EChartsOption{} + err := json.Unmarshal([]byte(tt.option), &opt) + assert.Nil(err) + assert.NotEmpty(opt.Series) + assert.NotEmpty(opt.ToOption().SeriesList) + } +} + +func TestRenderEChartsToSVG(t *testing.T) { + assert := assert.New(t) + + data, err := RenderEChartsToSVG(`{ "title": { "text": "Rainfall vs Evaporation", "subtext": "Fake Data" }, - "tooltip": { - "trigger": "axis" - }, "legend": { "data": [ "Rainfall", "Evaporation" ] }, - "toolbox": { - "show": true, - "feature": { - "dataView": { - "show": true, - "readOnly": false - }, - "magicType": { - "show": true, - "type": [ - "line", - "bar" - ] - }, - "restore": { - "show": true - }, - "saveAsImage": { - "show": true - } - } - }, - "calculable": true, + "padding": [10, 30, 10, 10], "xAxis": [ { "type": "category", @@ -169,11 +504,6 @@ func TestEChartsOption(t *testing.T) { ] } ], - "yAxis": [ - { - "type": "value" - } - ], "series": [ { "name": "Rainfall", @@ -195,20 +525,17 @@ func TestEChartsOption(t *testing.T) { "markPoint": { "data": [ { - "type": "max", - "name": "Max" + "type": "max" }, { - "type": "min", - "name": "Min" + "type": "min" } ] }, "markLine": { "data": [ { - "type": "average", - "name": "Avg" + "type": "average" } ] } @@ -233,31 +560,23 @@ func TestEChartsOption(t *testing.T) { "markPoint": { "data": [ { - "name": "Max", - "value": 182.2, - "xAxis": 7, - "yAxis": 183 + "type": "max" }, { - "name": "Min", - "value": 2.3, - "xAxis": 11, - "yAxis": 3 + "type": "min" } ] }, "markLine": { "data": [ { - "type": "average", - "name": "Avg" + "type": "average" } ] } } ] - }`), &opt) - + }`) assert.Nil(err) - assert.NotEmpty(opt.Series) + assert.Equal("\\nRainfallEvaporationRainfall vs EvaporationFake Data24020016012080400JanFebMarAprMayJunJulAugSepOctNovDec162.22182.22.341.6248.07", string(data)) } diff --git a/legend_test.go b/legend_test.go index 9078006..526f178 100644 --- a/legend_test.go +++ b/legend_test.go @@ -23,7 +23,6 @@ package charts import ( - "fmt" "testing" "github.com/stretchr/testify/assert" @@ -98,7 +97,6 @@ func TestNewLegend(t *testing.T) { assert.Nil(err) data, err := tt.render(p) assert.Nil(err) - fmt.Println(string(data)) assert.Equal(tt.result, string(data)) } } diff --git a/radar_chart_test.go b/radar_chart_test.go index baf616d..79fd9ac 100644 --- a/radar_chart_test.go +++ b/radar_chart_test.go @@ -23,7 +23,6 @@ package charts import ( - "fmt" "testing" "github.com/stretchr/testify/assert" @@ -103,7 +102,6 @@ func TestRadarChart(t *testing.T) { Bottom: 20, }))) assert.Nil(err) - fmt.Println(string(data)) assert.Equal(tt.result, string(data)) } }