refactor: add examples

This commit is contained in:
vicanso 2021-12-25 09:29:39 +08:00
parent c289ba7cde
commit 6aad7b6067
10 changed files with 231 additions and 46 deletions

View file

@ -1 +1,47 @@
# go-chart # go-echarts
![Alt](https://repobeats.axiom.co/api/embed/9071915842d72a909465be75eb6c12ffb7de2dcf.svg "Repobeats analytics image")
[go-chart](https://github.com/wcharczuk/go-chart)是golang常用的可视化图表库支持`svg``png`的输出,`Apache ECharts`在前端开发中得到众多开发者的认可。go-echarts则是结合两者的方式兼容`Apache ECharts`的配置参数,简单快捷的生成相似的图表(`svg``png`)方便插入至Email或分享使用。下面为常用的几种图表截图
![](./assets/go-echarts.jpg)
## 支持图表类型
暂仅支持三种的图表类型:`line`, `bar` 以及 `pie`
## 示例
`go-echarts`兼容了`echarts`的参数配置可简单的使用json形式的配置字符串则可快速生成图表。
```go
package main
import (
"os"
charts "github.com/vicanso/echarts"
)
func main() {
buf, err := charts.RenderEChartsToPNG(`{
"title": {
"text": "Line"
},
"xAxis": {
"type": "category",
"data": ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
},
"series": [
{
"data": [150, 230, 224, 218, 135, 147, 260]
}
]
}`)
if err != nil {
panic(err)
}
os.WriteFile("output.png", buf, 0600)
}
```

BIN
assets/go-echarts.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

16
axis.go
View file

@ -50,6 +50,16 @@ type YAxisOption struct {
const axisStrokeWidth = 1 const axisStrokeWidth = 1
func maxInt(values ...int) int {
result := 0
for _, v := range values {
if v > result {
result = v
}
}
return result
}
// GetXAxisAndValues returns x axis by theme, and the values of axis. // GetXAxisAndValues returns x axis by theme, and the values of axis.
func GetXAxisAndValues(xAxis XAxis, tickPosition chart.TickPosition, theme string) (chart.XAxis, []float64) { func GetXAxisAndValues(xAxis XAxis, tickPosition chart.TickPosition, theme string) (chart.XAxis, []float64) {
data := xAxis.Data data := xAxis.Data
@ -66,10 +76,8 @@ func GetXAxisAndValues(xAxis XAxis, tickPosition chart.TickPosition, theme strin
xValues := make([]float64, size) xValues := make([]float64, size)
ticks := make([]chart.Tick, 0) ticks := make([]chart.Tick, 0)
maxTicks := xAxis.SplitNumber // tick width
if maxTicks == 0 { maxTicks := maxInt(xAxis.SplitNumber, 10)
maxTicks = 10
}
// 计息最多每个unit至少放多个 // 计息最多每个unit至少放多个
minUnitSize := originalSize / maxTicks minUnitSize := originalSize / maxTicks

View file

@ -188,6 +188,7 @@ type ECharsOptions struct {
Data []ECharsSeriesData `json:"data"` Data []ECharsSeriesData `json:"data"`
Type string `json:"type"` Type string `json:"type"`
YAxisIndex int `json:"yAxisIndex"` YAxisIndex int `json:"yAxisIndex"`
ItemStyle EChartStyle `json:"itemStyle"`
} `json:"series"` } `json:"series"`
} }
@ -201,7 +202,15 @@ func convertEChartsSeries(e *ECharsOptions) ([]Series, chart.TickPosition) {
if seriesType == SeriesPie { if seriesType == SeriesPie {
series := make([]Series, len(e.Series[0].Data)) series := make([]Series, len(e.Series[0].Data))
for index, item := range e.Series[0].Data { for index, item := range e.Series[0].Data {
style := chart.Style{}
if item.ItemStyle.Color != "" {
c := parseColor(item.ItemStyle.Color)
style.FillColor = c
style.StrokeColor = c
}
series[index] = Series{ series[index] = Series{
Style: style,
Data: []SeriesData{ Data: []SeriesData{
{ {
Value: item.Value, Value: item.Value,
@ -219,6 +228,12 @@ func convertEChartsSeries(e *ECharsOptions) ([]Series, chart.TickPosition) {
if item.Type == SeriesBar { if item.Type == SeriesBar {
tickPosition = chart.TickPositionBetweenTicks tickPosition = chart.TickPositionBetweenTicks
} }
style := chart.Style{}
if item.ItemStyle.Color != "" {
c := parseColor(item.ItemStyle.Color)
style.FillColor = c
style.StrokeColor = c
}
data := make([]SeriesData, len(item.Data)) data := make([]SeriesData, len(item.Data))
for j, itemData := range item.Data { for j, itemData := range item.Data {
sd := SeriesData{ sd := SeriesData{
@ -232,6 +247,7 @@ func convertEChartsSeries(e *ECharsOptions) ([]Series, chart.TickPosition) {
data[j] = sd data[j] = sd
} }
series[index] = Series{ series[index] = Series{
Style: style,
YAxisIndex: item.YAxisIndex, YAxisIndex: item.YAxisIndex,
Data: data, Data: data,
Type: item.Type, Type: item.Type,
@ -317,3 +333,23 @@ func ParseECharsOptions(options string) (Options, error) {
return e.ToOptions(), nil return e.ToOptions(), nil
} }
func echartsRender(options string, rp chart.RendererProvider) ([]byte, error) {
o, err := ParseECharsOptions(options)
if err != nil {
return nil, err
}
g, err := New(o)
if err != nil {
return nil, err
}
return render(g, rp)
}
func RenderEChartsToPNG(options string) ([]byte, error) {
return echartsRender(options, chart.PNG)
}
func RenderEChartsToSVG(options string) ([]byte, error) {
return echartsRender(options, chart.SVG)
}

View file

@ -28,6 +28,7 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/wcharczuk/go-chart/v2" "github.com/wcharczuk/go-chart/v2"
"github.com/wcharczuk/go-chart/v2/drawing"
) )
func TestConvertToArray(t *testing.T) { func TestConvertToArray(t *testing.T) {
@ -288,6 +289,9 @@ func TestParseECharsOptions(t *testing.T) {
{ {
"name": "Precipitation", "name": "Precipitation",
"type": "bar", "type": "bar",
"itemStyle": {
"color": "#0052d9"
},
"data": [2.6, 5.9, 9, 26.4, 28.7, 70.7, 175.6] "data": [2.6, 5.9, 9, 26.4, 28.7, 70.7, 175.6]
}, },
{ {
@ -368,6 +372,20 @@ func TestParseECharsOptions(t *testing.T) {
2.6, 5.9, 9, 26.4, 28.7, 70.7, 175.6, 2.6, 5.9, 9, 26.4, 28.7, 70.7, 175.6,
}), }),
Type: SeriesBar, Type: SeriesBar,
Style: chart.Style{
StrokeColor: drawing.Color{
R: 0,
G: 82,
B: 217,
A: 255,
},
FillColor: drawing.Color{
R: 0,
G: 82,
B: 217,
A: 255,
},
},
}, },
{ {
Data: NewSeriesDataListFromFloat([]float64{ Data: NewSeriesDataListFromFloat([]float64{

28
examples/basic/main.go Normal file
View file

@ -0,0 +1,28 @@
package main
import (
"os"
charts "github.com/vicanso/echarts"
)
func main() {
buf, err := charts.RenderEChartsToPNG(`{
"title": {
"text": "Line"
},
"xAxis": {
"type": "category",
"data": ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
},
"series": [
{
"data": [150, 230, 224, 218, 135, 147, 260]
}
]
}`)
if err != nil {
panic(err)
}
os.WriteFile("output.png", buf, 0600)
}

View file

@ -9,32 +9,44 @@ import (
var html = `<!DOCTYPE html> var html = `<!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" /> <link rel="icon" href="/favicon.ico" />
<link type="text/css" rel="styleSheet" href="https://unpkg.com/normalize.css@8.0.1/normalize.css" /> <link type="text/css" rel="styleSheet" href="https://unpkg.com/normalize.css@8.0.1/normalize.css" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<style> <style>
h1 { .charts {
text-align: center; width: 830px;
} margin: 10px auto;
pre { overflow: hidden;
width: 800px; }
margin: auto auto 20px auto; .grid {
max-height: 300px; float: left;
overflow: auto; margin-right: 10px;
display: block; }
} .grid svg {
svg{ margin-bottom: 10px;
margin: auto auto 50px auto; }
display: block; h1 {
} text-align: center;
</style> }
<title>go-echarts</title> pre {
</head> width: 100%;
<body> margin: auto auto 20px auto;
{{body}} max-height: 300px;
</body> overflow: auto;
display: block;
}
svg{
margin: auto auto 50px auto;
display: block;
}
</style>
<title>go-echarts</title>
</head>
<body>
<div class="charts">{{body}}</div>
</body>
</html> </html>
` `
@ -43,10 +55,9 @@ var chartOptions = []map[string]string{
"title": "折线图", "title": "折线图",
"option": `{ "option": `{
"title": { "title": {
"text": "line", "text": "Line",
"textAlign": "left", "textAlign": "left",
"textStyle": { "textStyle": {
"color": "#333",
"fontSize": 24, "fontSize": 24,
"height": 40 "height": 40
} }
@ -214,6 +225,9 @@ var chartOptions = []map[string]string{
{ {
"name": "Evaporation", "name": "Evaporation",
"type": "bar", "type": "bar",
"itemStyle": {
"color": "#0052d9"
},
"data": [2, 4.9, 7, 23.2, 25.6, 76.7, 135.6] "data": [2, 4.9, 7, 23.2, 25.6, 76.7, 135.6]
}, },
{ {
@ -241,7 +255,7 @@ var chartOptions = []map[string]string{
}, },
"xAxis": { "xAxis": {
"type": "category", "type": "category",
"splitNumber": 12, "splitNumber": 6,
"data": ["01-01","01-02","01-03","01-04","01-05","01-06","01-07","01-08","01-09","01-10","01-11","01-12","01-13","01-14","01-15","01-16","01-17","01-18","01-19","01-20","01-21","01-22","01-23","01-24","01-25","01-26","01-27","01-28","01-29","01-30","01-31"] "data": ["01-01","01-02","01-03","01-04","01-05","01-06","01-07","01-08","01-09","01-10","01-11","01-12","01-13","01-14","01-15","01-16","01-17","01-18","01-19","01-20","01-21","01-22","01-23","01-24","01-25","01-26","01-27","01-28","01-29","01-30","01-31"]
}, },
"yAxis": { "yAxis": {
@ -300,19 +314,35 @@ var chartOptions = []map[string]string{
}, },
} }
func render(theme string) ([]byte, error) { type renderOptions struct {
theme string
width int
height int
onlyCharts bool
}
func render(opts renderOptions) ([]byte, error) {
data := bytes.Buffer{} data := bytes.Buffer{}
for _, m := range chartOptions { for _, m := range chartOptions {
// if m["title"] != "多柱状图" {
// continue
// }
chartHTML := []byte(`<div> chartHTML := []byte(`<div>
<h1>{{title}}</h1> <h1>{{title}}</h1>
<pre>{{option}}</pre> <pre>{{option}}</pre>
{{svg}} {{svg}}
</div>`) </div>`)
if opts.onlyCharts {
chartHTML = []byte(`<div class="grid">
{{svg}}
</div>`)
}
o, err := charts.ParseECharsOptions(m["option"]) o, err := charts.ParseECharsOptions(m["option"])
o.Theme = theme if opts.width > 0 {
o.Width = opts.width
}
if opts.height > 0 {
o.Height = opts.height
}
o.Theme = opts.theme
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -333,8 +363,17 @@ func render(theme string) ([]byte, error) {
} }
func indexHandler(w http.ResponseWriter, r *http.Request) { func indexHandler(w http.ResponseWriter, r *http.Request) {
query := r.URL.Query()
opts := renderOptions{
theme: query.Get("theme"),
}
if query.Get("view") == "grid" {
opts.width = 400
opts.height = 200
opts.onlyCharts = true
}
buf, err := render(r.URL.Query().Get("theme")) buf, err := render(opts)
if err != nil { if err != nil {
w.WriteHeader(400) w.WriteHeader(400)
w.Write([]byte(err.Error())) w.Write([]byte(err.Error()))

6
go.mod
View file

@ -9,9 +9,9 @@ require (
) )
require ( require (
github.com/davecgh/go-spew v1.1.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect
golang.org/x/image v0.0.0-20200927104501-e162460cd6b5 // indirect golang.org/x/image v0.0.0-20211028202545-6944b10bf410 // indirect
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
) )

11
go.sum
View file

@ -1,5 +1,6 @@
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
@ -11,10 +12,14 @@ github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5Cc
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/wcharczuk/go-chart/v2 v2.1.0 h1:tY2slqVQ6bN+yHSnDYwZebLQFkphK4WNrVwnt7CJZ2I= github.com/wcharczuk/go-chart/v2 v2.1.0 h1:tY2slqVQ6bN+yHSnDYwZebLQFkphK4WNrVwnt7CJZ2I=
github.com/wcharczuk/go-chart/v2 v2.1.0/go.mod h1:yx7MvAVNcP/kN9lKXM/NTce4au4DFN99j6i1OwDclNA= github.com/wcharczuk/go-chart/v2 v2.1.0/go.mod h1:yx7MvAVNcP/kN9lKXM/NTce4au4DFN99j6i1OwDclNA=
golang.org/x/image v0.0.0-20200927104501-e162460cd6b5 h1:QelT11PB4FXiDEXucrfNckHoFxwt8USGY1ajP1ZF5lM=
golang.org/x/image v0.0.0-20200927104501-e162460cd6b5/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20200927104501-e162460cd6b5/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.0.0-20211028202545-6944b10bf410 h1:hTftEOvwiOq2+O8k2D5/Q7COC7k5Qcrgc2TFURJYnvQ=
golang.org/x/image v0.0.0-20211028202545-6944b10bf410/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View file

@ -37,6 +37,7 @@ type Series struct {
Data []SeriesData Data []SeriesData
XValues []float64 XValues []float64
YAxisIndex int YAxisIndex int
Style chart.Style
} }
const lineStrokeWidth = 2 const lineStrokeWidth = 2
@ -75,6 +76,10 @@ func GetSeries(series []Series, tickPosition chart.TickPosition, theme string) [
DotColor: getSeriesColor(theme, index), DotColor: getSeriesColor(theme, index),
DotWidth: dotWith, DotWidth: dotWith,
} }
if !item.Style.StrokeColor.IsZero() {
style.StrokeColor = item.Style.StrokeColor
style.DotColor = item.Style.StrokeColor
}
pointIndexOffset := 0 pointIndexOffset := 0
// 如果居中,需要多增加一个点 // 如果居中,需要多增加一个点
if tickPosition == chart.TickPositionBetweenTicks { if tickPosition == chart.TickPositionBetweenTicks {