test: add test for axis

This commit is contained in:
vicanso 2022-06-23 20:32:25 +08:00
parent 706896737b
commit 8c5647f65f
10 changed files with 374 additions and 9 deletions

View file

@ -32,6 +32,8 @@ type GridPainterOption struct {
StrokeWidth float64
// The stroke color
StrokeColor Color
// The spans of column
ColumnSpans []int
// The column of grid
Column int
// The row of grid
@ -81,6 +83,7 @@ func (g *gridPainter) Render() (Box, error) {
})
g.p.Grid(GridOption{
Column: opt.Column,
ColumnSpans: opt.ColumnSpans,
Row: opt.Row,
IgnoreColumnLines: ignoreColumnLines,
IgnoreRowLines: ignoreRowLines,

View file

@ -54,6 +54,24 @@ func TestGrid(t *testing.T) {
},
result: "<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" width=\"600\" height=\"400\">\\n<path d=\"M 100 0\nL 100 400\" style=\"stroke-width:1;stroke:rgba(0,0,0,1.0);fill:none\"/><path d=\"M 200 0\nL 200 400\" style=\"stroke-width:1;stroke:rgba(0,0,0,1.0);fill:none\"/><path d=\"M 300 0\nL 300 400\" style=\"stroke-width:1;stroke:rgba(0,0,0,1.0);fill:none\"/><path d=\"M 400 0\nL 400 400\" style=\"stroke-width:1;stroke:rgba(0,0,0,1.0);fill:none\"/><path d=\"M 500 0\nL 500 400\" style=\"stroke-width:1;stroke:rgba(0,0,0,1.0);fill:none\"/><path d=\"M 0 66\nL 600 66\" style=\"stroke-width:1;stroke:rgba(0,0,0,1.0);fill:none\"/><path d=\"M 0 133\nL 600 133\" style=\"stroke-width:1;stroke:rgba(0,0,0,1.0);fill:none\"/><path d=\"M 0 200\nL 600 200\" style=\"stroke-width:1;stroke:rgba(0,0,0,1.0);fill:none\"/><path d=\"M 0 266\nL 600 266\" style=\"stroke-width:1;stroke:rgba(0,0,0,1.0);fill:none\"/><path d=\"M 0 333\nL 600 333\" style=\"stroke-width:1;stroke:rgba(0,0,0,1.0);fill:none\"/></svg>",
},
{
render: func(p *Painter) ([]byte, error) {
_, err := NewGridPainter(p, GridPainterOption{
StrokeColor: drawing.ColorBlack,
ColumnSpans: []int{
2,
5,
3,
},
Row: 6,
}).Render()
if err != nil {
return nil, err
}
return p.Bytes()
},
result: "<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" width=\"600\" height=\"400\">\\n<path d=\"M 0 0\nL 0 400\" style=\"stroke-width:1;stroke:rgba(0,0,0,1.0);fill:none\"/><path d=\"M 120 0\nL 120 400\" style=\"stroke-width:1;stroke:rgba(0,0,0,1.0);fill:none\"/><path d=\"M 420 0\nL 420 400\" style=\"stroke-width:1;stroke:rgba(0,0,0,1.0);fill:none\"/><path d=\"M 600 0\nL 600 400\" style=\"stroke-width:1;stroke:rgba(0,0,0,1.0);fill:none\"/><path d=\"M 0 0\nL 600 0\" style=\"stroke-width:1;stroke:rgba(0,0,0,1.0);fill:none\"/><path d=\"M 0 66\nL 600 66\" style=\"stroke-width:1;stroke:rgba(0,0,0,1.0);fill:none\"/><path d=\"M 0 133\nL 600 133\" style=\"stroke-width:1;stroke:rgba(0,0,0,1.0);fill:none\"/><path d=\"M 0 200\nL 600 200\" style=\"stroke-width:1;stroke:rgba(0,0,0,1.0);fill:none\"/><path d=\"M 0 266\nL 600 266\" style=\"stroke-width:1;stroke:rgba(0,0,0,1.0);fill:none\"/><path d=\"M 0 333\nL 600 333\" style=\"stroke-width:1;stroke:rgba(0,0,0,1.0);fill:none\"/><path d=\"M 0 400\nL 600 400\" style=\"stroke-width:1;stroke:rgba(0,0,0,1.0);fill:none\"/></svg>",
},
}
for _, tt := range tests {
p, err := NewPainter(PainterOptions{

View file

@ -71,6 +71,7 @@ type MultiTextOption struct {
type GridOption struct {
Column int
Row int
ColumnSpans []int
// 忽略不展示的column
IgnoreColumnLines []int
// 忽略不展示的row
@ -542,6 +543,9 @@ func (p *Painter) TextFit(body string, x, y, width int) chart.Box {
var output chart.Box
for index, line := range lines {
if line == "" {
continue
}
x0 := x
y0 := y + output.Height()
p.Text(line, x0, y0)
@ -690,8 +694,12 @@ func (p *Painter) Grid(opt GridOption) *Painter {
})
}
}
if opt.Column > 0 {
values := autoDivide(width, opt.Column)
columnCount := sumInt(opt.ColumnSpans)
if columnCount == 0 {
columnCount = opt.Column
}
if columnCount > 0 {
values := autoDivideSpans(width, columnCount, opt.ColumnSpans)
drawLines(values, opt.IgnoreColumnLines, true)
}
if opt.Row > 0 {

89
series_test.go Normal file
View file

@ -0,0 +1,89 @@
// 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"
)
func TestNewSeriesListDataFromValues(t *testing.T) {
assert := assert.New(t)
assert.Equal(SeriesList{
{
Type: ChartTypeBar,
Data: []SeriesData{
{
Value: 1.0,
},
},
},
}, NewSeriesListDataFromValues([][]float64{
{
1,
},
}, ChartTypeBar))
}
func TestSeriesLists(t *testing.T) {
assert := assert.New(t)
seriesList := NewSeriesListDataFromValues([][]float64{
{
1,
2,
},
{
10,
},
}, ChartTypeBar)
assert.Equal(2, len(seriesList.Filter(ChartTypeBar)))
assert.Equal(0, len(seriesList.Filter(ChartTypeLine)))
max, min := seriesList.GetMaxMin(0)
assert.Equal(float64(10), max)
assert.Equal(float64(1), min)
assert.Equal(seriesSummary{
MaxIndex: 1,
MaxValue: 2,
MinIndex: 0,
MinValue: 1,
AverageValue: 1.5,
}, seriesList[0].Summary())
}
func TestFormatter(t *testing.T) {
assert := assert.New(t)
assert.Equal("a: 12%", NewPieLabelFormatter([]string{
"a",
"b",
}, "")(0, 10, 0.12))
assert.Equal("10", NewValueLabelFormatter([]string{
"a",
"b",
}, "")(0, 10, 0.12))
}

53
table.go Normal file
View file

@ -0,0 +1,53 @@
// 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
type tableChart struct {
p *Painter
opt *TableChartOption
}
func NewTableChart(p *Painter, opt TableChartOption) *tableChart {
if opt.Theme == nil {
opt.Theme = defaultTheme
}
return &tableChart{
p: p,
opt: &opt,
}
}
type TableChartOption struct {
// The theme
Theme ColorPalette
// The padding of table cell
Padding Box
// The header data of table
HeaderData []string
// The data of table
Data [][]string
}
func (c *tableChart) Render() (Box, error) {
return BoxZero, nil
}

View file

@ -97,6 +97,9 @@ func (t *titlePainter) Render() (Box, error) {
p := t.p
theme := opt.Theme
if theme == nil {
theme = p.theme
}
if opt.Text == "" && opt.Subtext == "" {
return BoxZero, nil
}

93
title_test.go Normal file
View file

@ -0,0 +1,93 @@
// 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"
)
func TestTitleRenderer(t *testing.T) {
assert := assert.New(t)
tests := []struct {
render func(*Painter) ([]byte, error)
result string
}{
{
render: func(p *Painter) ([]byte, error) {
_, err := NewTitlePainter(p, TitleOption{
Text: "title",
Subtext: "subTitle",
Left: "20",
Top: "20",
}).Render()
if err != nil {
return nil, err
}
return p.Bytes()
},
result: "<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" width=\"600\" height=\"400\">\\n<text x=\"34\" y=\"35\" style=\"stroke-width:0;stroke:none;fill:rgba(70,70,70,1.0);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">title</text><text x=\"20\" y=\"50\" style=\"stroke-width:0;stroke:none;fill:rgba(70,70,70,1.0);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">subTitle</text></svg>",
},
{
render: func(p *Painter) ([]byte, error) {
_, err := NewTitlePainter(p, TitleOption{
Text: "title",
Subtext: "subTitle",
Left: "20%",
Top: "20",
}).Render()
if err != nil {
return nil, err
}
return p.Bytes()
},
result: "<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" width=\"600\" height=\"400\">\\n<text x=\"134\" y=\"35\" style=\"stroke-width:0;stroke:none;fill:rgba(70,70,70,1.0);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">title</text><text x=\"120\" y=\"50\" style=\"stroke-width:0;stroke:none;fill:rgba(70,70,70,1.0);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">subTitle</text></svg>",
},
{
render: func(p *Painter) ([]byte, error) {
_, err := NewTitlePainter(p, TitleOption{
Text: "title",
Subtext: "subTitle",
Left: PositionRight,
}).Render()
if err != nil {
return nil, err
}
return p.Bytes()
},
result: "<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" width=\"600\" height=\"400\">\\n<text x=\"558\" y=\"15\" style=\"stroke-width:0;stroke:none;fill:rgba(70,70,70,1.0);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">title</text><text x=\"544\" y=\"30\" style=\"stroke-width:0;stroke:none;fill:rgba(70,70,70,1.0);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">subTitle</text></svg>",
},
}
for _, tt := range tests {
p, err := NewPainter(PainterOptions{
Type: ChartOutputSVG,
Width: 600,
Height: 400,
}, PainterThemeOption(defaultTheme))
assert.Nil(err)
data, err := tt.render(p)
assert.Nil(err)
assert.Equal(tt.result, string(data))
}
}

24
util.go
View file

@ -90,6 +90,30 @@ func autoDivide(max, size int) []int {
return values
}
func autoDivideSpans(max, size int, spans []int) []int {
values := autoDivide(max, size)
// 重新合并
if len(spans) != 0 {
newValues := make([]int, len(spans)+1)
newValues[0] = 0
end := 0
for index, v := range spans {
end += v
newValues[index+1] = values[end]
}
values = newValues
}
return values
}
func sumInt(values []int) int {
sum := 0
for _, v := range values {
sum += v
}
return sum
}
// measureTextMaxWidthHeight returns maxWidth and maxHeight of text list
func measureTextMaxWidthHeight(textList []string, p *Painter) (int, int) {
maxWidth := 0

View file

@ -65,14 +65,18 @@ func NewYAxisOptions(data []string, others ...[]string) []YAxisOption {
return opts
}
func (opt *YAxisOption) ToAxisOption() AxisOption {
func (opt *YAxisOption) ToAxisOption(p *Painter) AxisOption {
position := PositionLeft
if opt.Position == PositionRight {
position = PositionRight
}
theme := opt.Theme
if theme == nil {
theme = p.theme
}
axisOpt := AxisOption{
Formatter: opt.Formatter,
Theme: opt.Theme,
Theme: theme,
Data: opt.Data,
Position: position,
FontSize: opt.FontSize,
@ -81,7 +85,7 @@ func (opt *YAxisOption) ToAxisOption() AxisOption {
FontColor: opt.FontColor,
BoundaryGap: FalseFlag(),
SplitLineShow: true,
SplitLineColor: opt.Theme.GetAxisSplitLineColor(),
SplitLineColor: theme.GetAxisSplitLineColor(),
Show: opt.Show,
}
if !opt.Color.IsZero() {
@ -101,7 +105,7 @@ func NewLeftYAxis(p *Painter, opt YAxisOption) *axisPainter {
p = p.Child(PainterPaddingOption(Box{
Bottom: defaultXAxisHeight,
}))
return NewAxisPainter(p, opt.ToAxisOption())
return NewAxisPainter(p, opt.ToAxisOption(p))
}
// NewRightYAxis returns a right y axis renderer
@ -109,7 +113,7 @@ func NewRightYAxis(p *Painter, opt YAxisOption) *axisPainter {
p = p.Child(PainterPaddingOption(Box{
Bottom: defaultXAxisHeight,
}))
axisOpt := opt.ToAxisOption()
axisOpt := opt.ToAxisOption(p)
axisOpt.Position = PositionRight
axisOpt.SplitLineShow = false
return NewAxisPainter(p, axisOpt)

70
yaxis_test.go Normal file
View file

@ -0,0 +1,70 @@
// 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"
)
func TestRightYAxis(t *testing.T) {
assert := assert.New(t)
tests := []struct {
render func(*Painter) ([]byte, error)
result string
}{
{
render: func(p *Painter) ([]byte, error) {
opt := NewYAxisOptions([]string{
"a",
"b",
"c",
"d",
})[0]
_, err := NewRightYAxis(p, opt).Render()
if err != nil {
return nil, err
}
return p.Bytes()
},
result: "<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" width=\"600\" height=\"400\">\\n<text x=\"581\" y=\"17\" style=\"stroke-width:0;stroke:none;fill:rgba(70,70,70,1.0);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">a</text><text x=\"581\" y=\"133\" style=\"stroke-width:0;stroke:none;fill:rgba(70,70,70,1.0);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">b</text><text x=\"581\" y=\"250\" style=\"stroke-width:0;stroke:none;fill:rgba(70,70,70,1.0);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">c</text><text x=\"581\" y=\"367\" style=\"stroke-width:0;stroke:none;fill:rgba(70,70,70,1.0);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">d</text></svg>",
},
}
for _, tt := range tests {
p, err := NewPainter(PainterOptions{
Type: ChartOutputSVG,
Width: 600,
Height: 400,
}, PainterThemeOption(defaultTheme), PainterPaddingOption(Box{
Top: 10,
Right: 10,
Bottom: 10,
Left: 10,
}))
assert.Nil(err)
data, err := tt.render(p)
assert.Nil(err)
assert.Equal(tt.result, string(data))
}
}