From b3a3018ea2eb6f71b126ce053f1d9410225a0689 Mon Sep 17 00:00:00 2001
From: vicanso
Date: Sat, 25 Jun 2022 08:21:27 +0800
Subject: [PATCH] feat: support table redner
---
README.md | 68 +++++++++++++++
README_zh.md | 64 ++++++++++++++
chart_option.go | 61 ++++++-------
examples/charts/main.go | 42 +++++++++
examples/table/main.go | 76 ++++++++--------
painter.go | 4 +
table.go | 188 ++++++++++++++++++++++++++++++----------
table_test.go | 46 +++++++++-
8 files changed, 433 insertions(+), 116 deletions(-)
diff --git a/README.md b/README.md
index d7d94f7..d0549f0 100644
--- a/README.md
+++ b/README.md
@@ -15,6 +15,9 @@ Screenshot of common charts, the left part is light theme, the right part is gra
+
+
+
## Chart Type
These chart types are supported: `line`, `bar`, `pie`, `radar` or `funnel`.
@@ -374,6 +377,71 @@ func main() {
}
```
+### Table
+
+```go
+package main
+
+import (
+ "github.com/vicanso/go-charts/v2"
+)
+
+func main() {
+ header := []string{
+ "Name",
+ "Age",
+ "Address",
+ "Tag",
+ "Action",
+ }
+ data := [][]string{
+ {
+ "John Brown",
+ "32",
+ "New York No. 1 Lake Park",
+ "nice, developer",
+ "Send Mail",
+ },
+ {
+ "Jim Green ",
+ "42",
+ "London No. 1 Lake Park",
+ "wow",
+ "Send Mail",
+ },
+ {
+ "Joe Black ",
+ "32",
+ "Sidney No. 1 Lake Park",
+ "cool, teacher",
+ "Send Mail",
+ },
+ }
+ spans := map[int]int{
+ 0: 2,
+ 1: 1,
+ // 设置第三列的span
+ 2: 3,
+ 3: 2,
+ 4: 2,
+ }
+ p, err := charts.TableRender(
+ header,
+ data,
+ spans,
+ )
+ if err != nil {
+ panic(err)
+ }
+
+ buf, err := p.Bytes()
+ if err != nil {
+ panic(err)
+ }
+ // snip...
+}
+```
+
### ECharts Render
```go
diff --git a/README_zh.md b/README_zh.md
index dbdaaf3..487a365 100644
--- a/README_zh.md
+++ b/README_zh.md
@@ -373,6 +373,70 @@ func main() {
}
```
+### Table
+
+```go
+package main
+
+import (
+ "github.com/vicanso/go-charts/v2"
+)
+
+func main() {
+ header := []string{
+ "Name",
+ "Age",
+ "Address",
+ "Tag",
+ "Action",
+ }
+ data := [][]string{
+ {
+ "John Brown",
+ "32",
+ "New York No. 1 Lake Park",
+ "nice, developer",
+ "Send Mail",
+ },
+ {
+ "Jim Green ",
+ "42",
+ "London No. 1 Lake Park",
+ "wow",
+ "Send Mail",
+ },
+ {
+ "Joe Black ",
+ "32",
+ "Sidney No. 1 Lake Park",
+ "cool, teacher",
+ "Send Mail",
+ },
+ }
+ spans := map[int]int{
+ 0: 2,
+ 1: 1,
+ // 设置第三列的span
+ 2: 3,
+ 3: 2,
+ 4: 2,
+ }
+ p, err := charts.TableRender(
+ header,
+ data,
+ spans,
+ )
+ if err != nil {
+ panic(err)
+ }
+
+ buf, err := p.Bytes()
+ if err != nil {
+ panic(err)
+ }
+ // snip...
+}
+```
### ECharts Render
```go
diff --git a/chart_option.go b/chart_option.go
index b7b4714..39de686 100644
--- a/chart_option.go
+++ b/chart_option.go
@@ -23,7 +23,6 @@
package charts
import (
- "fmt"
"sort"
"github.com/golang/freetype/truetype"
@@ -338,24 +337,44 @@ func FunnelRender(values []float64, opts ...OptionFunc) (*Painter, error) {
}, opts...)
}
-func TableRender(opt TableChartOption) (*Painter, error) {
+// TableRender table chart render
+func TableRender(header []string, data [][]string, spanMaps ...map[int]int) (*Painter, error) {
+ opt := TableChartOption{
+ Header: header,
+ Data: data,
+ }
+ if len(spanMaps) != 0 {
+ spanMap := spanMaps[0]
+ spans := make([]int, len(opt.Header))
+ for index := range spans {
+ v, ok := spanMap[index]
+ if !ok {
+ v = 1
+ }
+ spans[index] = v
+ }
+ opt.Spans = spans
+ }
+ return TableOptionRender(opt)
+}
+
+// TableOptionRender table render with option
+func TableOptionRender(opt TableChartOption) (*Painter, error) {
if opt.Type == "" {
opt.Type = ChartOutputPNG
}
if opt.Width <= 0 {
opt.Width = defaultChartWidth
}
- if opt.Height <= 0 {
- opt.Height = defaultChartHeight
- }
if opt.Font == nil {
opt.Font, _ = chart.GetDefaultFont()
}
p, err := NewPainter(PainterOptions{
- Type: opt.Type,
- Width: opt.Width,
- Height: opt.Height,
+ Type: opt.Type,
+ Width: opt.Width,
+ // 仅用于计算表格高度,因此随便设置即可
+ Height: 100,
Font: opt.Font,
})
if err != nil {
@@ -365,8 +384,6 @@ func TableRender(opt TableChartOption) (*Painter, error) {
if err != nil {
return nil, err
}
- fmt.Println(*info)
- fmt.Println(info.Height)
p, err = NewPainter(PainterOptions{
Type: opt.Type,
@@ -377,31 +394,9 @@ func TableRender(opt TableChartOption) (*Painter, error) {
if err != nil {
return nil, err
}
- _, err = NewTableChart(p, opt).Render()
+ _, err = NewTableChart(p, opt).renderWithInfo(info)
if err != nil {
return nil, err
}
-
- // opt := ChartOption{}
- // for _, fn := range opts {
- // fn(&opt)
- // }
- // opt.fillDefault()
- // p, err := NewPainter(PainterOptions{
- // Type: opt.Type,
- // Width: opt.Width,
- // Height: opt.Height,
- // Font: opt.font,
- // })
- // if err != nil {
- // return nil, err
- // }
- // _, err = NewTableChart(p, TableChartOption{
- // Header: header,
- // Data: data,
- // }).Render()
- // if err != nil {
- // return nil, err
- // }
return p, nil
}
diff --git a/examples/charts/main.go b/examples/charts/main.go
index 0e1d48e..7b14919 100644
--- a/examples/charts/main.go
+++ b/examples/charts/main.go
@@ -92,6 +92,48 @@ func handler(w http.ResponseWriter, req *http.Request, chartOptions []charts.Cha
bytesList = append(bytesList, buf)
}
+ p, err := charts.TableOptionRender(charts.TableChartOption{
+ Type: charts.ChartOutputSVG,
+ Header: []string{
+ "Name",
+ "Age",
+ "Address",
+ "Tag",
+ "Action",
+ },
+ Data: [][]string{
+ {
+ "John Brown",
+ "32",
+ "New York No. 1 Lake Park",
+ "nice, developer",
+ "Send Mail",
+ },
+ {
+ "Jim Green ",
+ "42",
+ "London No. 1 Lake Park",
+ "wow",
+ "Send Mail",
+ },
+ {
+ "Joe Black ",
+ "32",
+ "Sidney No. 1 Lake Park",
+ "cool, teacher",
+ "Send Mail",
+ },
+ },
+ })
+ if err != nil {
+ panic(err)
+ }
+ buf, err := p.Bytes()
+ if err != nil {
+ panic(err)
+ }
+ bytesList = append(bytesList, buf)
+
data := bytes.ReplaceAll([]byte(html), []byte("{{body}}"), bytes.Join(bytesList, []byte("")))
w.Header().Set("Content-Type", "text/html")
w.Write(data)
diff --git a/examples/table/main.go b/examples/table/main.go
index 650566c..ee8147e 100644
--- a/examples/table/main.go
+++ b/examples/table/main.go
@@ -24,45 +24,49 @@ func writeFile(buf []byte) error {
}
func main() {
- p, err := charts.TableRender(charts.TableChartOption{
- Header: []string{
- "Name",
- "Age",
- "Address",
- "Tag",
- "Action",
+ charts.SetDefaultWidth(810)
+ header := []string{
+ "Name",
+ "Age",
+ "Address",
+ "Tag",
+ "Action",
+ }
+ data := [][]string{
+ {
+ "John Brown",
+ "32",
+ "New York No. 1 Lake Park",
+ "nice, developer",
+ "Send Mail",
},
- Spans: []int{
- 1,
- 1,
- 2,
- 1,
- 1,
+ {
+ "Jim Green ",
+ "42",
+ "London No. 1 Lake Park",
+ "wow",
+ "Send Mail",
},
- Data: [][]string{
- {
- "John Brown",
- "32",
- "New York No. 1 Lake Park",
- "nice, developer",
- "Send Mail",
- },
- {
- "Jim Green ",
- "42",
- "London No. 1 Lake Park",
- "wow",
- "Send Mail",
- },
- {
- "Joe Black ",
- "32",
- "Sidney No. 1 Lake Park",
- "cool, teacher",
- "Send Mail",
- },
+ {
+ "Joe Black ",
+ "32",
+ "Sidney No. 1 Lake Park",
+ "cool, teacher",
+ "Send Mail",
},
- },
+ }
+ spans := map[int]int{
+ 0: 2,
+ 1: 1,
+ // 设置第三列的span
+ 2: 3,
+ 3: 2,
+ 4: 2,
+ }
+ p, err := charts.TableRender(
+ header,
+ data,
+ spans,
)
if err != nil {
panic(err)
diff --git a/painter.go b/painter.go
index c787315..06973b6 100644
--- a/painter.go
+++ b/painter.go
@@ -38,6 +38,8 @@ type Painter struct {
parent *Painter
style Style
theme ColorPalette
+ // 类型
+ outputType string
}
type PainterOptions struct {
@@ -169,6 +171,8 @@ func NewPainter(opts PainterOptions, opt ...PainterOption) (*Painter, error) {
Bottom: opts.Height,
},
font: font,
+ // 类型
+ outputType: opts.Type,
}
p.setOptions(opt...)
if p.theme == nil {
diff --git a/table.go b/table.go
index 34dee67..ffa7013 100644
--- a/table.go
+++ b/table.go
@@ -35,6 +35,7 @@ type tableChart struct {
opt *TableChartOption
}
+// NewTableChart returns a table chart render
func NewTableChart(p *Painter, opt TableChartOption) *tableChart {
if opt.Theme == nil {
opt.Theme = defaultTheme
@@ -50,8 +51,6 @@ type TableChartOption struct {
Type string
// The width of table
Width int
- // The height of table
- Height int
// The theme
Theme ColorPalette
// The padding of table cell
@@ -64,7 +63,9 @@ type TableChartOption struct {
Spans []int
// The font size of table
FontSize float64
- Font *truetype.Font
+ // The font family, which should be installed first
+ FontFamily string
+ Font *truetype.Font
// The font color of table
FontColor Color
// The background color of header
@@ -77,26 +78,101 @@ type TableChartOption struct {
BackgroundColor Color
}
-var defaultTableHeaderColor = Color{
- R: 34,
- G: 34,
- B: 34,
- A: 255,
+type TableSetting struct {
+ // The color of header
+ HeaderColor Color
+ // The color of heder text
+ HeaderFontColor Color
+ // The color of table text
+ FontColor Color
+ // The color list of row
+ RowColors []Color
+ // The padding of cell
+ Padding Box
}
-var defaultTableRowColors = []Color{
- drawing.ColorWhite,
- {
- R: 242,
- G: 242,
- B: 242,
+
+var TableLightThemeSetting = TableSetting{
+ HeaderColor: Color{
+ R: 240,
+ G: 240,
+ B: 240,
A: 255,
},
+ HeaderFontColor: Color{
+ R: 98,
+ G: 105,
+ B: 118,
+ A: 255,
+ },
+ FontColor: Color{
+ R: 70,
+ G: 70,
+ B: 70,
+ A: 255,
+ },
+ RowColors: []Color{
+ drawing.ColorWhite,
+ {
+ R: 247,
+ G: 247,
+ B: 247,
+ A: 255,
+ },
+ },
+ Padding: Box{
+ Left: 10,
+ Top: 10,
+ Right: 10,
+ Bottom: 10,
+ },
}
-var defaultTablePadding = Box{
- Left: 10,
- Top: 10,
- Right: 10,
- Bottom: 10,
+
+var TableDarkThemeSetting = TableSetting{
+ HeaderColor: Color{
+ R: 38,
+ G: 38,
+ B: 42,
+ A: 255,
+ },
+ HeaderFontColor: Color{
+ R: 216,
+ G: 217,
+ B: 218,
+ A: 255,
+ },
+ FontColor: Color{
+ R: 216,
+ G: 217,
+ B: 218,
+ A: 255,
+ },
+ RowColors: []Color{
+ {
+ R: 24,
+ G: 24,
+ B: 28,
+ A: 255,
+ },
+ {
+ R: 38,
+ G: 38,
+ B: 42,
+ A: 255,
+ },
+ },
+ Padding: Box{
+ Left: 10,
+ Top: 10,
+ Right: 10,
+ Bottom: 10,
+ },
+}
+
+var tableDefaultSetting = TableLightThemeSetting
+
+// SetDefaultTableSetting sets the default setting for table
+func SetDefaultTableSetting(setting TableSetting) {
+ tableDefaultSetting = setting
}
type renderInfo struct {
@@ -106,12 +182,12 @@ type renderInfo struct {
RowHeights []int
}
-func (c *tableChart) render() (*renderInfo, error) {
+func (t *tableChart) render() (*renderInfo, error) {
info := renderInfo{
RowHeights: make([]int, 0),
}
- p := c.p
- opt := c.opt
+ p := t.p
+ opt := t.opt
if len(opt.Header) == 0 {
return nil, errors.New("header can not be nil")
}
@@ -125,7 +201,7 @@ func (c *tableChart) render() (*renderInfo, error) {
}
fontColor := opt.FontColor
if fontColor.IsZero() {
- fontColor = theme.GetTextColor()
+ fontColor = tableDefaultSetting.FontColor
}
font := opt.Font
if font == nil {
@@ -133,14 +209,14 @@ func (c *tableChart) render() (*renderInfo, error) {
}
headerFontColor := opt.HeaderFontColor
if opt.HeaderFontColor.IsZero() {
- headerFontColor = drawing.ColorWhite
+ headerFontColor = tableDefaultSetting.HeaderFontColor
}
spans := opt.Spans
- if len(spans) != 0 && len(spans) != len(opt.Header) {
+ if len(spans) != len(opt.Header) {
newSpans := make([]int, len(opt.Header))
for index := range opt.Header {
- if len(spans) < index {
+ if index >= len(spans) {
newSpans[index] = 1
} else {
newSpans[index] = spans[index]
@@ -149,7 +225,8 @@ func (c *tableChart) render() (*renderInfo, error) {
spans = newSpans
}
- values := autoDivideSpans(p.Width(), len(opt.Header)+1, spans)
+ sum := sumInt(spans)
+ values := autoDivideSpans(p.Width(), sum, spans)
height := 0
textStyle := Style{
FontSize: fontSize,
@@ -162,7 +239,7 @@ func (c *tableChart) render() (*renderInfo, error) {
headerHeight := 0
padding := opt.Padding
if padding.IsZero() {
- padding = defaultTablePadding
+ padding = tableDefaultSetting.Padding
}
renderTableCells := func(textList []string, currentHeight int, cellPadding Box) int {
@@ -201,34 +278,23 @@ func (c *tableChart) render() (*renderInfo, error) {
return &info, nil
}
-func (c *tableChart) Render() (Box, error) {
- p := c.p
- opt := c.opt
+func (t *tableChart) renderWithInfo(info *renderInfo) (Box, error) {
+ p := t.p
+ opt := t.opt
if !opt.BackgroundColor.IsZero() {
p.SetBackground(p.Width(), p.Height(), opt.BackgroundColor)
}
headerBGColor := opt.HeaderBackgroundColor
if headerBGColor.IsZero() {
- headerBGColor = defaultTableHeaderColor
+ headerBGColor = tableDefaultSetting.HeaderColor
}
- r := p.render
- newRender, err := chart.SVG(p.Width(), 100)
- if err != nil {
- return BoxZero, err
- }
- p.render = newRender
- info, err := c.render()
- if err != nil {
- return BoxZero, err
- }
- p.render = r
// 如果设置表头背景色
p.SetBackground(info.Width, info.HeaderHeight, headerBGColor, true)
currentHeight := info.HeaderHeight
rowColors := opt.RowBackgroundColors
if len(rowColors) == 0 {
- rowColors = defaultTableRowColors
+ rowColors = tableDefaultSetting.RowColors
}
for index, h := range info.RowHeights {
color := rowColors[index%len(rowColors)]
@@ -238,7 +304,7 @@ func (c *tableChart) Render() (Box, error) {
child.SetBackground(p.Width(), h, color, true)
currentHeight += h
}
- _, err = c.render()
+ _, err := t.render()
if err != nil {
return BoxZero, err
}
@@ -248,3 +314,35 @@ func (c *tableChart) Render() (Box, error) {
Bottom: info.Height,
}, nil
}
+
+func (t *tableChart) Render() (Box, error) {
+ p := t.p
+ opt := t.opt
+ if !opt.BackgroundColor.IsZero() {
+ p.SetBackground(p.Width(), p.Height(), opt.BackgroundColor)
+ }
+ headerBGColor := opt.HeaderBackgroundColor
+ if headerBGColor.IsZero() {
+ headerBGColor = tableDefaultSetting.HeaderColor
+ }
+ if opt.Font == nil && opt.FontFamily != "" {
+ opt.Font, _ = GetFont(opt.FontFamily)
+ }
+
+ r := p.render
+ fn := chart.PNG
+ if p.outputType == ChartOutputSVG {
+ fn = chart.SVG
+ }
+ newRender, err := fn(p.Width(), 100)
+ if err != nil {
+ return BoxZero, err
+ }
+ p.render = newRender
+ info, err := t.render()
+ if err != nil {
+ return BoxZero, err
+ }
+ p.render = r
+ return t.renderWithInfo(info)
+}
diff --git a/table_test.go b/table_test.go
index c54de25..41a857c 100644
--- a/table_test.go
+++ b/table_test.go
@@ -51,7 +51,8 @@ func TestTableChart(t *testing.T) {
1,
2,
1,
- 1,
+ // span和header不匹配,最后自动设置为1
+ // 1,
},
Data: [][]string{
{
@@ -82,6 +83,48 @@ func TestTableChart(t *testing.T) {
}
return p.Bytes()
},
+ result: "",
+ },
+ {
+ render: func(p *Painter) ([]byte, error) {
+ _, err := NewTableChart(p, TableChartOption{
+ Header: []string{
+ "Name",
+ "Age",
+ "Address",
+ "Tag",
+ "Action",
+ },
+ Data: [][]string{
+ {
+ "John Brown",
+ "32",
+ "New York No. 1 Lake Park",
+ "nice, developer",
+ "Send Mail",
+ },
+ {
+ "Jim Green ",
+ "42",
+ "London No. 1 Lake Park",
+ "wow",
+ "Send Mail",
+ },
+ {
+ "Joe Black ",
+ "32",
+ "Sidney No. 1 Lake Park",
+ "cool, teacher",
+ "Send Mail",
+ },
+ },
+ }).Render()
+ if err != nil {
+ return nil, err
+ }
+ return p.Bytes()
+ },
+ result: "",
},
}
for _, tt := range tests {
@@ -96,5 +139,4 @@ func TestTableChart(t *testing.T) {
fmt.Println(string(data))
assert.Equal(tt.result, string(data))
}
-
}