diff --git a/grid.go b/grid.go
index fb5dad6..0ebd226 100644
--- a/grid.go
+++ b/grid.go
@@ -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,
diff --git a/grid_test.go b/grid_test.go
index f6880dc..3110a2b 100644
--- a/grid_test.go
+++ b/grid_test.go
@@ -54,6 +54,24 @@ func TestGrid(t *testing.T) {
},
result: "",
},
+ {
+ 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: "",
+ },
}
for _, tt := range tests {
p, err := NewPainter(PainterOptions{
diff --git a/painter.go b/painter.go
index c250369..1f9d418 100644
--- a/painter.go
+++ b/painter.go
@@ -69,8 +69,9 @@ type MultiTextOption struct {
}
type GridOption struct {
- Column int
- Row int
+ 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 {
diff --git a/series_test.go b/series_test.go
new file mode 100644
index 0000000..40d2f91
--- /dev/null
+++ b/series_test.go
@@ -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))
+}
diff --git a/table.go b/table.go
new file mode 100644
index 0000000..e47914c
--- /dev/null
+++ b/table.go
@@ -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
+}
diff --git a/title.go b/title.go
index 5af4c39..5cdd161 100644
--- a/title.go
+++ b/title.go
@@ -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
}
diff --git a/title_test.go b/title_test.go
new file mode 100644
index 0000000..add8163
--- /dev/null
+++ b/title_test.go
@@ -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: "",
+ },
+ {
+ 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: "",
+ },
+ {
+ 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: "",
+ },
+ }
+ 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))
+ }
+}
diff --git a/util.go b/util.go
index adfa9fd..a33c6d2 100644
--- a/util.go
+++ b/util.go
@@ -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
diff --git a/yaxis.go b/yaxis.go
index b0bfa86..eb9034c 100644
--- a/yaxis.go
+++ b/yaxis.go
@@ -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)
diff --git a/yaxis_test.go b/yaxis_test.go
new file mode 100644
index 0000000..0f565ac
--- /dev/null
+++ b/yaxis_test.go
@@ -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: "",
+ },
+ }
+ 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))
+ }
+}