feat: support table redner

This commit is contained in:
vicanso 2022-06-25 08:21:27 +08:00
parent 2fb0ebcbf7
commit b3a3018ea2
8 changed files with 433 additions and 116 deletions

View file

@ -15,6 +15,9 @@ Screenshot of common charts, the left part is light theme, the right part is gra
<img src="./assets/go-charts.png" alt="go-charts"> <img src="./assets/go-charts.png" alt="go-charts">
</p> </p>
<p align="center">
<img src="./assets/go-table.png" alt="go-table">
</p>
## Chart Type ## Chart Type
These chart types are supported: `line`, `bar`, `pie`, `radar` or `funnel`. 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 ### ECharts Render
```go ```go

View file

@ -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 ### ECharts Render
```go ```go

View file

@ -23,7 +23,6 @@
package charts package charts
import ( import (
"fmt"
"sort" "sort"
"github.com/golang/freetype/truetype" "github.com/golang/freetype/truetype"
@ -338,24 +337,44 @@ func FunnelRender(values []float64, opts ...OptionFunc) (*Painter, error) {
}, opts...) }, 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 == "" { if opt.Type == "" {
opt.Type = ChartOutputPNG opt.Type = ChartOutputPNG
} }
if opt.Width <= 0 { if opt.Width <= 0 {
opt.Width = defaultChartWidth opt.Width = defaultChartWidth
} }
if opt.Height <= 0 {
opt.Height = defaultChartHeight
}
if opt.Font == nil { if opt.Font == nil {
opt.Font, _ = chart.GetDefaultFont() opt.Font, _ = chart.GetDefaultFont()
} }
p, err := NewPainter(PainterOptions{ p, err := NewPainter(PainterOptions{
Type: opt.Type, Type: opt.Type,
Width: opt.Width, Width: opt.Width,
Height: opt.Height, // 仅用于计算表格高度,因此随便设置即可
Height: 100,
Font: opt.Font, Font: opt.Font,
}) })
if err != nil { if err != nil {
@ -365,8 +384,6 @@ func TableRender(opt TableChartOption) (*Painter, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
fmt.Println(*info)
fmt.Println(info.Height)
p, err = NewPainter(PainterOptions{ p, err = NewPainter(PainterOptions{
Type: opt.Type, Type: opt.Type,
@ -377,31 +394,9 @@ func TableRender(opt TableChartOption) (*Painter, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
_, err = NewTableChart(p, opt).Render() _, err = NewTableChart(p, opt).renderWithInfo(info)
if err != nil { if err != nil {
return nil, err 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 return p, nil
} }

View file

@ -92,6 +92,48 @@ func handler(w http.ResponseWriter, req *http.Request, chartOptions []charts.Cha
bytesList = append(bytesList, buf) 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(""))) data := bytes.ReplaceAll([]byte(html), []byte("{{body}}"), bytes.Join(bytesList, []byte("")))
w.Header().Set("Content-Type", "text/html") w.Header().Set("Content-Type", "text/html")
w.Write(data) w.Write(data)

View file

@ -24,45 +24,49 @@ func writeFile(buf []byte) error {
} }
func main() { func main() {
p, err := charts.TableRender(charts.TableChartOption{ charts.SetDefaultWidth(810)
Header: []string{ header := []string{
"Name", "Name",
"Age", "Age",
"Address", "Address",
"Tag", "Tag",
"Action", "Action",
}
data := [][]string{
{
"John Brown",
"32",
"New York No. 1 Lake Park",
"nice, developer",
"Send Mail",
}, },
Spans: []int{ {
1, "Jim Green ",
1, "42",
2, "London No. 1 Lake Park",
1, "wow",
1, "Send Mail",
}, },
Data: [][]string{ {
{ "Joe Black ",
"John Brown", "32",
"32", "Sidney No. 1 Lake Park",
"New York No. 1 Lake Park", "cool, teacher",
"nice, developer", "Send Mail",
"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 { if err != nil {
panic(err) panic(err)

View file

@ -38,6 +38,8 @@ type Painter struct {
parent *Painter parent *Painter
style Style style Style
theme ColorPalette theme ColorPalette
// 类型
outputType string
} }
type PainterOptions struct { type PainterOptions struct {
@ -169,6 +171,8 @@ func NewPainter(opts PainterOptions, opt ...PainterOption) (*Painter, error) {
Bottom: opts.Height, Bottom: opts.Height,
}, },
font: font, font: font,
// 类型
outputType: opts.Type,
} }
p.setOptions(opt...) p.setOptions(opt...)
if p.theme == nil { if p.theme == nil {

188
table.go
View file

@ -35,6 +35,7 @@ type tableChart struct {
opt *TableChartOption opt *TableChartOption
} }
// NewTableChart returns a table chart render
func NewTableChart(p *Painter, opt TableChartOption) *tableChart { func NewTableChart(p *Painter, opt TableChartOption) *tableChart {
if opt.Theme == nil { if opt.Theme == nil {
opt.Theme = defaultTheme opt.Theme = defaultTheme
@ -50,8 +51,6 @@ type TableChartOption struct {
Type string Type string
// The width of table // The width of table
Width int Width int
// The height of table
Height int
// The theme // The theme
Theme ColorPalette Theme ColorPalette
// The padding of table cell // The padding of table cell
@ -64,7 +63,9 @@ type TableChartOption struct {
Spans []int Spans []int
// The font size of table // The font size of table
FontSize float64 FontSize float64
Font *truetype.Font // The font family, which should be installed first
FontFamily string
Font *truetype.Font
// The font color of table // The font color of table
FontColor Color FontColor Color
// The background color of header // The background color of header
@ -77,26 +78,101 @@ type TableChartOption struct {
BackgroundColor Color BackgroundColor Color
} }
var defaultTableHeaderColor = Color{ type TableSetting struct {
R: 34, // The color of header
G: 34, HeaderColor Color
B: 34, // The color of heder text
A: 255, 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, var TableLightThemeSetting = TableSetting{
{ HeaderColor: Color{
R: 242, R: 240,
G: 242, G: 240,
B: 242, B: 240,
A: 255, 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, var TableDarkThemeSetting = TableSetting{
Top: 10, HeaderColor: Color{
Right: 10, R: 38,
Bottom: 10, 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 { type renderInfo struct {
@ -106,12 +182,12 @@ type renderInfo struct {
RowHeights []int RowHeights []int
} }
func (c *tableChart) render() (*renderInfo, error) { func (t *tableChart) render() (*renderInfo, error) {
info := renderInfo{ info := renderInfo{
RowHeights: make([]int, 0), RowHeights: make([]int, 0),
} }
p := c.p p := t.p
opt := c.opt opt := t.opt
if len(opt.Header) == 0 { if len(opt.Header) == 0 {
return nil, errors.New("header can not be nil") return nil, errors.New("header can not be nil")
} }
@ -125,7 +201,7 @@ func (c *tableChart) render() (*renderInfo, error) {
} }
fontColor := opt.FontColor fontColor := opt.FontColor
if fontColor.IsZero() { if fontColor.IsZero() {
fontColor = theme.GetTextColor() fontColor = tableDefaultSetting.FontColor
} }
font := opt.Font font := opt.Font
if font == nil { if font == nil {
@ -133,14 +209,14 @@ func (c *tableChart) render() (*renderInfo, error) {
} }
headerFontColor := opt.HeaderFontColor headerFontColor := opt.HeaderFontColor
if opt.HeaderFontColor.IsZero() { if opt.HeaderFontColor.IsZero() {
headerFontColor = drawing.ColorWhite headerFontColor = tableDefaultSetting.HeaderFontColor
} }
spans := opt.Spans spans := opt.Spans
if len(spans) != 0 && len(spans) != len(opt.Header) { if len(spans) != len(opt.Header) {
newSpans := make([]int, len(opt.Header)) newSpans := make([]int, len(opt.Header))
for index := range opt.Header { for index := range opt.Header {
if len(spans) < index { if index >= len(spans) {
newSpans[index] = 1 newSpans[index] = 1
} else { } else {
newSpans[index] = spans[index] newSpans[index] = spans[index]
@ -149,7 +225,8 @@ func (c *tableChart) render() (*renderInfo, error) {
spans = newSpans spans = newSpans
} }
values := autoDivideSpans(p.Width(), len(opt.Header)+1, spans) sum := sumInt(spans)
values := autoDivideSpans(p.Width(), sum, spans)
height := 0 height := 0
textStyle := Style{ textStyle := Style{
FontSize: fontSize, FontSize: fontSize,
@ -162,7 +239,7 @@ func (c *tableChart) render() (*renderInfo, error) {
headerHeight := 0 headerHeight := 0
padding := opt.Padding padding := opt.Padding
if padding.IsZero() { if padding.IsZero() {
padding = defaultTablePadding padding = tableDefaultSetting.Padding
} }
renderTableCells := func(textList []string, currentHeight int, cellPadding Box) int { renderTableCells := func(textList []string, currentHeight int, cellPadding Box) int {
@ -201,34 +278,23 @@ func (c *tableChart) render() (*renderInfo, error) {
return &info, nil return &info, nil
} }
func (c *tableChart) Render() (Box, error) { func (t *tableChart) renderWithInfo(info *renderInfo) (Box, error) {
p := c.p p := t.p
opt := c.opt opt := t.opt
if !opt.BackgroundColor.IsZero() { if !opt.BackgroundColor.IsZero() {
p.SetBackground(p.Width(), p.Height(), opt.BackgroundColor) p.SetBackground(p.Width(), p.Height(), opt.BackgroundColor)
} }
headerBGColor := opt.HeaderBackgroundColor headerBGColor := opt.HeaderBackgroundColor
if headerBGColor.IsZero() { 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) p.SetBackground(info.Width, info.HeaderHeight, headerBGColor, true)
currentHeight := info.HeaderHeight currentHeight := info.HeaderHeight
rowColors := opt.RowBackgroundColors rowColors := opt.RowBackgroundColors
if len(rowColors) == 0 { if len(rowColors) == 0 {
rowColors = defaultTableRowColors rowColors = tableDefaultSetting.RowColors
} }
for index, h := range info.RowHeights { for index, h := range info.RowHeights {
color := rowColors[index%len(rowColors)] color := rowColors[index%len(rowColors)]
@ -238,7 +304,7 @@ func (c *tableChart) Render() (Box, error) {
child.SetBackground(p.Width(), h, color, true) child.SetBackground(p.Width(), h, color, true)
currentHeight += h currentHeight += h
} }
_, err = c.render() _, err := t.render()
if err != nil { if err != nil {
return BoxZero, err return BoxZero, err
} }
@ -248,3 +314,35 @@ func (c *tableChart) Render() (Box, error) {
Bottom: info.Height, Bottom: info.Height,
}, nil }, 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)
}

View file

@ -51,7 +51,8 @@ func TestTableChart(t *testing.T) {
1, 1,
2, 2,
1, 1,
1, // span和header不匹配最后自动设置为1
// 1,
}, },
Data: [][]string{ Data: [][]string{
{ {
@ -82,6 +83,48 @@ func TestTableChart(t *testing.T) {
} }
return p.Bytes() 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 600 0\nL 600 35\nL 0 35\nL 0 0\" style=\"stroke-width:0;stroke:none;fill:rgba(240,240,240,1.0)\"/><path d=\"M 0 35\nL 600 35\nL 600 90\nL 0 90\nL 0 35\" style=\"stroke-width:0;stroke:none;fill:rgba(255,255,255,1.0)\"/><path d=\"M 0 90\nL 600 90\nL 600 125\nL 0 125\nL 0 90\" style=\"stroke-width:0;stroke:none;fill:rgba(247,247,247,1.0)\"/><path d=\"M 0 125\nL 600 125\nL 600 180\nL 0 180\nL 0 125\" style=\"stroke-width:0;stroke:none;fill:rgba(255,255,255,1.0)\"/><text x=\"10\" y=\"22\" style=\"stroke-width:0;stroke:none;fill:rgba(98,105,118,1.0);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">Name</text><text x=\"110\" y=\"22\" style=\"stroke-width:0;stroke:none;fill:rgba(98,105,118,1.0);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">Age</text><text x=\"210\" y=\"22\" style=\"stroke-width:0;stroke:none;fill:rgba(98,105,118,1.0);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">Address</text><text x=\"410\" y=\"22\" style=\"stroke-width:0;stroke:none;fill:rgba(98,105,118,1.0);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">Tag</text><text x=\"510\" y=\"22\" style=\"stroke-width:0;stroke:none;fill:rgba(98,105,118,1.0);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">Action</text><text x=\"10\" y=\"57\" style=\"stroke-width:0;stroke:none;fill:rgba(70,70,70,1.0);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">John</text><text x=\"10\" y=\"77\" style=\"stroke-width:0;stroke:none;fill:rgba(70,70,70,1.0);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">Brown</text><text x=\"110\" y=\"57\" style=\"stroke-width:0;stroke:none;fill:rgba(70,70,70,1.0);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">32</text><text x=\"210\" y=\"57\" style=\"stroke-width:0;stroke:none;fill:rgba(70,70,70,1.0);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">New York No. 1 Lake Park</text><text x=\"410\" y=\"57\" style=\"stroke-width:0;stroke:none;fill:rgba(70,70,70,1.0);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">nice,</text><text x=\"410\" y=\"77\" style=\"stroke-width:0;stroke:none;fill:rgba(70,70,70,1.0);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">developer</text><text x=\"510\" y=\"57\" style=\"stroke-width:0;stroke:none;fill:rgba(70,70,70,1.0);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">Send Mail</text><text x=\"10\" y=\"112\" style=\"stroke-width:0;stroke:none;fill:rgba(70,70,70,1.0);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">Jim Green</text><text x=\"110\" y=\"112\" style=\"stroke-width:0;stroke:none;fill:rgba(70,70,70,1.0);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">42</text><text x=\"210\" y=\"112\" style=\"stroke-width:0;stroke:none;fill:rgba(70,70,70,1.0);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">London No. 1 Lake Park</text><text x=\"410\" y=\"112\" style=\"stroke-width:0;stroke:none;fill:rgba(70,70,70,1.0);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">wow</text><text x=\"510\" y=\"112\" style=\"stroke-width:0;stroke:none;fill:rgba(70,70,70,1.0);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">Send Mail</text><text x=\"10\" y=\"147\" style=\"stroke-width:0;stroke:none;fill:rgba(70,70,70,1.0);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">Joe Black</text><text x=\"110\" y=\"147\" style=\"stroke-width:0;stroke:none;fill:rgba(70,70,70,1.0);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">32</text><text x=\"210\" y=\"147\" style=\"stroke-width:0;stroke:none;fill:rgba(70,70,70,1.0);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">Sidney No. 1 Lake Park</text><text x=\"410\" y=\"147\" style=\"stroke-width:0;stroke:none;fill:rgba(70,70,70,1.0);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">cool,</text><text x=\"410\" y=\"167\" style=\"stroke-width:0;stroke:none;fill:rgba(70,70,70,1.0);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">teacher</text><text x=\"510\" y=\"147\" style=\"stroke-width:0;stroke:none;fill:rgba(70,70,70,1.0);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">Send Mail</text></svg>",
},
{
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: "<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 600 0\nL 600 35\nL 0 35\nL 0 0\" style=\"stroke-width:0;stroke:none;fill:rgba(240,240,240,1.0)\"/><path d=\"M 0 35\nL 600 35\nL 600 90\nL 0 90\nL 0 35\" style=\"stroke-width:0;stroke:none;fill:rgba(255,255,255,1.0)\"/><path d=\"M 0 90\nL 600 90\nL 600 145\nL 0 145\nL 0 90\" style=\"stroke-width:0;stroke:none;fill:rgba(247,247,247,1.0)\"/><path d=\"M 0 145\nL 600 145\nL 600 200\nL 0 200\nL 0 145\" style=\"stroke-width:0;stroke:none;fill:rgba(255,255,255,1.0)\"/><text x=\"10\" y=\"22\" style=\"stroke-width:0;stroke:none;fill:rgba(98,105,118,1.0);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">Name</text><text x=\"130\" y=\"22\" style=\"stroke-width:0;stroke:none;fill:rgba(98,105,118,1.0);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">Age</text><text x=\"250\" y=\"22\" style=\"stroke-width:0;stroke:none;fill:rgba(98,105,118,1.0);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">Address</text><text x=\"370\" y=\"22\" style=\"stroke-width:0;stroke:none;fill:rgba(98,105,118,1.0);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">Tag</text><text x=\"490\" y=\"22\" style=\"stroke-width:0;stroke:none;fill:rgba(98,105,118,1.0);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">Action</text><text x=\"10\" y=\"57\" style=\"stroke-width:0;stroke:none;fill:rgba(70,70,70,1.0);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">John Brown</text><text x=\"130\" y=\"57\" style=\"stroke-width:0;stroke:none;fill:rgba(70,70,70,1.0);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">32</text><text x=\"250\" y=\"57\" style=\"stroke-width:0;stroke:none;fill:rgba(70,70,70,1.0);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">New York No.</text><text x=\"250\" y=\"77\" style=\"stroke-width:0;stroke:none;fill:rgba(70,70,70,1.0);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">1 Lake Park</text><text x=\"370\" y=\"57\" style=\"stroke-width:0;stroke:none;fill:rgba(70,70,70,1.0);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">nice,</text><text x=\"370\" y=\"77\" style=\"stroke-width:0;stroke:none;fill:rgba(70,70,70,1.0);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">developer</text><text x=\"490\" y=\"57\" style=\"stroke-width:0;stroke:none;fill:rgba(70,70,70,1.0);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">Send Mail</text><text x=\"10\" y=\"112\" style=\"stroke-width:0;stroke:none;fill:rgba(70,70,70,1.0);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">Jim Green</text><text x=\"130\" y=\"112\" style=\"stroke-width:0;stroke:none;fill:rgba(70,70,70,1.0);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">42</text><text x=\"250\" y=\"112\" style=\"stroke-width:0;stroke:none;fill:rgba(70,70,70,1.0);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">London No. 1</text><text x=\"250\" y=\"132\" style=\"stroke-width:0;stroke:none;fill:rgba(70,70,70,1.0);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">Lake Park</text><text x=\"370\" y=\"112\" style=\"stroke-width:0;stroke:none;fill:rgba(70,70,70,1.0);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">wow</text><text x=\"490\" y=\"112\" style=\"stroke-width:0;stroke:none;fill:rgba(70,70,70,1.0);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">Send Mail</text><text x=\"10\" y=\"167\" style=\"stroke-width:0;stroke:none;fill:rgba(70,70,70,1.0);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">Joe Black</text><text x=\"130\" y=\"167\" style=\"stroke-width:0;stroke:none;fill:rgba(70,70,70,1.0);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">32</text><text x=\"250\" y=\"167\" style=\"stroke-width:0;stroke:none;fill:rgba(70,70,70,1.0);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">Sidney No. 1</text><text x=\"250\" y=\"187\" style=\"stroke-width:0;stroke:none;fill:rgba(70,70,70,1.0);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">Lake Park</text><text x=\"370\" y=\"167\" style=\"stroke-width:0;stroke:none;fill:rgba(70,70,70,1.0);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">cool, teacher</text><text x=\"490\" y=\"167\" style=\"stroke-width:0;stroke:none;fill:rgba(70,70,70,1.0);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">Send Mail</text></svg>",
}, },
} }
for _, tt := range tests { for _, tt := range tests {
@ -96,5 +139,4 @@ func TestTableChart(t *testing.T) {
fmt.Println(string(data)) fmt.Println(string(data))
assert.Equal(tt.result, string(data)) assert.Equal(tt.result, string(data))
} }
} }