简介

ECharts是一款基于JavaScript的数据可视化图表库,提供直观,生动,可交互,可个性化定制的数据可视化图表。ECharts最初由百度团队开源,并于2018年初捐赠给Apache基金会,成为ASF孵化级项目。

在 Golang 这门语言中,目前数据可视化的第三方库还是特别少,go-echarts 的开发就是为了填补这部分的空隙。Apache ECharts 是非常优秀的可视化图表库,凭借着良好的交互性,精巧的图表设计,得到了众多开发者的认可。也有其他语言为其实现了相应语言版本的接口,如 Python 的 pyecharts,go-echarts 也是借鉴了 pyecharts 的一些设计思想。

特性

  • 简洁的 API 设计,使用如丝滑般流畅
  • 囊括了 25+ 种常见图表,应有尽有
  • 高度灵活的配置项,可轻松搭配出精美的图表
  • 详细的文档和示例,帮助开发者更快的上手项目
  • 多达 400+ 地图,为地理数据可视化提供强有力的支持

如何使用

1.安装go-echarts库

go get -u github.com/go-echarts/go-echarts/v2

2.接着,我们来创建一个图表实例,绘制一个条形图,来演示如何使用。

package main

import (
	"math/rand"
	"os"

	"github.com/go-echarts/go-echarts/v2/charts"
	"github.com/go-echarts/go-echarts/v2/opts"
)

// generate random data for bar chartg
func generateBarItems() []opts.BarData {
	items := make([]opts.BarData, 0)
	for i := 0; i < 7; i++ {
		items = append(items, opts.BarData{Value: rand.Intn(300)})
	}
	return items
}

func main() {
	//创建一个新的bar实例
	bar := charts.NewBar()
	//设置一些全局选项,如标题/图例/工具提示或其他任何东西
	//example: 标题/子标题
	bar.SetGlobalOptions(charts.WithTitleOpts(opts.Title{
		Title:    "My first bar chart generated by go-echarts",
		Subtitle: "It's extremely easy to use, right?",
	}))

	//将数据放入实例
	bar.SetXAxis([]string{"Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"}).
		AddSeries("Category A", generateBarItems()).
		AddSeries("Category B", generateBarItems())
	//奇迹发生的地方-绘图生成html
	f, _ := os.Create("bar.html")
	bar.Render(f)
}

运行上例,会生成bar.html,详细:

  1. 如果想把上例改成折线图呢
package main

import (
	"github.com/go-echarts/go-echarts/v2/charts"
	"github.com/go-echarts/go-echarts/v2/opts"
	"math/rand"
	"os"
)

// generate random data for line chart
func generateLineItems() []opts.LineData {
	items := make([]opts.LineData, 0)
	for i := 0; i < 7; i++ {
		items = append(items, opts.LineData{Value: rand.Intn(300)})
	}
	return items
}

func main() {
	//创建一个折线图实例
	line := charts.NewLine()
	//设置一些全局选项,如标题/图例/工具提示或其他任何东西
	//example: 标题/子标题
	line.SetGlobalOptions(charts.WithTitleOpts(opts.Title{
		Title:    "My first lines chart generated by go-echarts",
		Subtitle: "It's extremely easy to use, right?",
	}))

	//将数据放入实例
	line.SetXAxis([]string{"Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"}).
		AddSeries("Category A", generateLineItems()).
		AddSeries("Category B", generateLineItems()).
		SetSeriesOptions(charts.WithLineChartOpts(opts.LineChart{Smooth: true}))

	//奇迹发生的地方-绘图生成html
	f, _ := os.Create("lines.html")
	line.Render(f)
}

运行上例,会生成lines.html,详细:

  1. 如果想直接生成图表的 http serve呢:
package main

import (
	"github.com/go-echarts/go-echarts/v2/charts"
	"github.com/go-echarts/go-echarts/v2/opts"
	"math/rand"
	"net/http"
)

// generate random data for line chart
func generateLineItems() []opts.LineData {
	items := make([]opts.LineData, 0)
	for i := 0; i < 7; i++ {
		items = append(items, opts.LineData{Value: rand.Intn(300)})
	}
	return items
}

func httpserver(w http.ResponseWriter, _ *http.Request) {
	//创建一个折线图实例
	line := charts.NewLine()
	//设置一些全局选项,如标题/图例/工具提示或其他任何东西
	//example: 标题/子标题
	line.SetGlobalOptions(charts.WithTitleOpts(opts.Title{
		Title:    "My first lines chart generated by go-echarts",
		Subtitle: "It's extremely easy to use, right?",
	}))

	//将数据放入实例
	line.SetXAxis([]string{"Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"}).
		AddSeries("Category A", generateLineItems()).
		AddSeries("Category B", generateLineItems()).
		SetSeriesOptions(charts.WithLineChartOpts(opts.LineChart{Smooth: true}))

	//奇迹发生的地方-绘图生成html
	line.Render(w)
}

func main() {
	http.HandleFunc("/", httpserver)
	http.ListenAndServe(":8080", nil)
}

访问,http://localhost:8080/ ,详细如下

三、总结

本文简要介绍go-echarts和使用方法,并通过绘制条形图、折线图和图表的http server 的例子演示了使用方法。更多功能待续。

参考

比较常见pprof 可以把调用栈可视化成调用图,embedded-struct-visualizer 可以把Go 的项目的代码分层结构和依赖都可视化成流程图。

安装 embedded-struct-visualizer

 go install github.com/davidschlachter/embedded-struct-visualizer@latest

查看命令选项参数

embedded-struct-visualizer -h
Usage: [OPTIONS] DirToScan
If the directory to scan is not provided, it defaults to './'
OPTIONS:
  -out <file>  path to output file (default: write to stdout)
  -v           verbose logging

Example

以官方给的例子 main.go

package main

import (
	"time"
)

type A struct {
	B
	C map[string]D
}

type B struct {
	E, F  string
	G     string
	Timer H
}

type D struct {
	I uint64
}

type H struct {
	Timer time.Ticker
	J     chan D
}

生成结构关系:

> embedded-struct-visualizer
digraph {
"main.A" -> { "main.B" "main.D" };
"main.B" -> { "main.H" };
"main.H" -> { "time.Ticker" "main.D" };
}

生成结构关系 并 输出到.gv 文件:

>embedded-struct-visualizer -out .\example.gv
> ls

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
-a----        2022/11/27     10:54            112 example.gv
-a----        2022/11/26     22:06             31 go.mod
-a----        2022/11/26     22:07            208 main.go

项目目录下多出了example.gv ,具体内容如下

cat .\example.gv
digraph {
"main.A" -> { "main.B" "main.D" };
"main.B" -> { "main.H" };
"main.H" -> { "time.Ticker" "main.D" };

安装 graphviz 

graphviz 是一种将结构信息表示为抽象图和网络的图的工具。

接着,把生成的gv文件利用graphviz绘制的更美观。

首先下载安装:

https://graphviz.org/download/ 查看是否安装成功(Win):

 dot -v
dot - graphviz version 7.0.2 (20221119.0110)
... <为了不篇幅过长,省略一些细节>

利用graphviz绘图

dot -Tformat[:renderer[:formatter]] -o 将输出语言设置为支持的格式之一。默认情况下,生成带属性的点。

接着把上一步生成的gv文件,生成PNG输出

dot -Tpng example.gv -o example.png

在项目目录下多出example.png

接着,实用于一个相对复杂的Gin Engine的gv

  1. 源码链接: https://github.com/gin-gonic/gin/blob/master/gin.go

  2. 生成的gv:

embedded-struct-visualizer -out .\gin.gv

gin.gv 详细如下:

digraph {"gin.mockWriter" -> { "http.Header" };"binding.QueryTest" -> { "binding.appkey" };"binding.FooBarStruct" -> { "binding.FooStruct" };"binding.FooBarFileStruct" -> { "binding.FooBarStruct" 
"multipart.FileHeader" };"binding.FooBarFileFailStruct" -> { "binding.FooBarStruct" 
"multipart.FileHeader" };"binding.FooDefaultBarStruct" -> { "binding.FooStruct" };"binding.FooStructUseNumber" -> { "binding.any" };"binding.FooStructDisallowUnknownFields" -> { "binding.any" 
};"binding.FooBarStructForTimeType" -> { "time.Time" };"binding.FooStructForTimeTypeNotUnixFormat" -> { "time.Time" 
};"binding.FooStructForTimeTypeNotFormat" -> { "time.Time" };"binding.FooStructForTimeTypeFailFormat" -> { "time.Time" };"binding.FooStructForTimeTypeFailLocation" -> { "time.Time" 
};"binding.FooStructForMapType" -> { "binding.any" };"binding.FooStructForIgnoreFormTag" -> { "binding.string" };"binding.defaultValidator" -> { "sync.Once" 
"validator.Validate" };"binding.structFull" -> { "binding." "time.Time" 
"binding.string" };"binding.S" -> { "binding.S" };"binding.testFile" -> { "binding.byte" };"binding.structNoValidationValues" -> { 
"binding.substructNoValidation" "binding.int16" 
"binding.uint16" "time.Time" "binding.mapNoValidationSub" 
"binding." };"binding.structNoValidationPointer" -> { 
"binding.substructNoValidation" "binding.uint32" 
"binding.mapNoValidationSub" "binding.int8" "binding.int32" 
"binding.uint8" "binding.uint16" "binding.float64" 
"binding.map" "binding.int" "binding.int16" "binding.int64" 
"binding.float32" "time.Time" "binding.testInterface" 
"binding.uint" "binding.uint64" "binding.string" };"gin.Context" -> { "url.Values" "gin.responseWriter" 
"gin.HandlersChain" "gin.any" "gin.errorMsgs" 
"gin.skippedNode" "sync.RWMutex" "gin.string" 
"http.SameSite" "http.Request" "gin.ResponseWriter" 
"gin.Params" "gin.Engine" };"gin.interceptedWriter" -> { "gin.ResponseWriter" 
"bytes.Buffer" };"gin.Error" -> { "gin.error" "gin.ErrorType" "gin.any" };"gin.onlyFilesFS" -> { "http.FileSystem" };"gin.neuteredReaddirFile" -> { "http.File" };"gin.RouteInfo" -> { "gin.HandlerFunc" };"gin.Engine" -> { "gin.RouterGroup" "gin.string" 
"render.HTMLRender" "gin.HandlersChain" "sync.Pool" 
"gin.methodTrees" "render.Delims" "template.FuncMap" 
"gin.uint16" "net.IPNet" };"gin.LoggerConfig" -> { "gin.LogFormatter" "io.Writer" 
"gin.string" };"gin.LogFormatterParams" -> { "http.Request" "time.Time" 
"time.Duration" "gin.any" };"render.Data" -> { "render.byte" };"render.HTMLProduction" -> { "template.Template" 
"render.Delims" };"render.HTMLDebug" -> { "render.string" "render.Delims" 
"template.FuncMap" };"render.HTML" -> { "template.Template" "render.any" };"render.JSON" -> { "render.any" };"render.IndentedJSON" -> { "render.any" };"render.SecureJSON" -> { "render.any" };"render.JsonpJSON" -> { "render.any" };"render.AsciiJSON" -> { "render.any" };"render.PureJSON" -> { "render.any" };"render.MsgPack" -> { "render.any" };"render.ProtoBuf" -> { "render.any" };"render.Reader" -> { "io.Reader" };"render.Redirect" -> { "http.Request" };"render.String" -> { "render.any" };"render.TOML" -> { "render.any" };"render.XML" -> { "render.any" };"render.YAML" -> { "render.any" };"gin.responseWriter" -> { "http.ResponseWriter" };"gin.RouterGroup" -> { "gin.HandlersChain" "gin.Engine" };"protoexample.Test" -> { "protoexample.int32" 
"protoexample.int64" "protoexample.TestOptionalGroup" 
"protoimpl.MessageState" "protoimpl.SizeCache" 
"protoimpl.UnknownFields" "protoexample.string" };"protoexample.Test_OptionalGroup" -> { 
"protoimpl.MessageState" "protoimpl.SizeCache" 
"protoimpl.UnknownFields" "protoexample.string" };"gin.methodTree" -> { "gin.node" };"gin.node" -> { "gin.node" "gin.HandlersChain" 
"gin.nodeType" };"gin.nodeValue" -> { "gin.HandlersChain" "gin.Params" };"gin.skippedNode" -> { "gin.node" "gin.int16" };}


  1. 绘制png
 dot -Tpng gin.gv -o gin.png

<ps: 图片过大可下载预览>

参考

jefffff

Stay hungry. Stay Foolish COOL

Go backend developer

China Amoy