Go echarts
last modified October 30, 2026
In this article we show how to create charts in Go with go-echarts. The library builds the configuration of an Apache ECharts chart and writes an HTML page that renders it in the browser.
Unlike image-based chart libraries, go-echarts does not draw pixels on the server. It emits a small HTML document with the chart data encoded as JSON and a script that initializes ECharts. The result is an interactive chart with tooltips and legends, provided the browser can load the ECharts JavaScript files referenced by the page.
All examples were compiled and run with Go 1.27.0 and go-echarts v2.7.3. Every
chart uses the dark theme that is built into ECharts. The generated files were
inspected with grep; the chart ID in each file is random, so the
file content differs on every run. The screenshots were taken in Chromium.
Installation
$ mkdir echarts-demo $ cd echarts-demo $ go mod init com.zetcode/echarts go: creating new go.mod: module com.zetcode/echarts $ go get github.com/go-echarts/go-echarts/v2@v2.7.3 go: added github.com/go-echarts/go-echarts/v2 v2.7.3
A line chart
A line chart is created with charts.NewLine. The global options
set the theme and the title, SetXAxis supplies the category labels
and AddSeries adds the values. Render writes the HTML
to any io.Writer.
package main
import (
"os"
"github.com/go-echarts/go-echarts/v2/charts"
"github.com/go-echarts/go-echarts/v2/opts"
)
func main() {
sals := []opts.LineData{{Value: 567}, {Value: 612}, {Value: 800},
{Value: 980}, {Value: 1410}, {Value: 2350}}
ages := []string{"18", "20", "25", "30", "40", "50"}
line := charts.NewLine()
line.SetGlobalOptions(
charts.WithInitializationOpts(opts.Initialization{Theme: "dark"}),
charts.WithTitleOpts(opts.Title{Title: "Average salary per age",
Subtitle: "Slovakia"}),
)
line.SetXAxis(ages).AddSeries("salaries", sals)
line.SetSeriesOptions(charts.WithLineChartOpts(
opts.LineChart{
Smooth: opts.Bool(true),
}))
f, err := os.Create("line.html")
if err != nil {
panic(err)
}
defer f.Close()
if err := line.Render(f); err != nil {
panic(err)
}
}
The chart options are written into a JavaScript object. The page loads the
ECharts library from the go-echarts assets site, so the chart needs a network
connection when it is opened. The dark theme is built into ECharts, so no theme
script is added; the preset themes such as westeros or chalk add one. The theme
name is a plain string, because v2.7.3 has no ThemeDark constant.
The title and the data can be found in the option object of the page. The
smooth flag turns the line into a curve; note that it is a
types.Bool, a pointer type, so it is set with
opts.Bool(true).
$ go run main.go
$ head -8 line.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Awesome go-echarts</title>
<script src="https://go-echarts.github.io/go-echarts-assets/assets/echarts.min.js"></script>
</head>
$ grep -o '"title":{[^}]*}' line.html
"title":{"text":"Average salary per age","subtext":"Slovakia"}
$ grep -o '"data":\[{"value":567}[^]]*\]' line.html
"data":[{"value":567},{"value":612},{"value":800},{"value":980},{"value":1410},{"value":2350}]
$ grep -o '"smooth":[a-z]*' line.html
"smooth":true
Several series
AddSeries can be called more than once. A legend and a tooltip
make the series distinguishable; the tooltip trigger axis shows
all values at the hovered position.
package main
import (
"os"
"github.com/go-echarts/go-echarts/v2/charts"
"github.com/go-echarts/go-echarts/v2/opts"
)
func main() {
months := []string{"Jan", "Feb", "Mar", "Apr", "May", "Jun"}
a := []opts.LineData{{Value: 120}, {Value: 132}, {Value: 101}, {Value: 134},
{Value: 90}, {Value: 230}}
b := []opts.LineData{{Value: 220}, {Value: 182}, {Value: 191}, {Value: 234},
{Value: 290}, {Value: 330}}
line := charts.NewLine()
line.SetGlobalOptions(
charts.WithInitializationOpts(opts.Initialization{Theme: "dark"}),
charts.WithTitleOpts(opts.Title{Title: "Monthly orders"}),
charts.WithLegendOpts(opts.Legend{Show: opts.Bool(true)}),
charts.WithTooltipOpts(opts.Tooltip{Show: opts.Bool(true)}),
)
line.SetXAxis(months).
AddSeries("2025", a).
AddSeries("2026", b)
f, err := os.Create("multi.html")
if err != nil {
panic(err)
}
defer f.Close()
if err := line.Render(f); err != nil {
panic(err)
}
}
Both series share the same x axis and the same chart options. The legend is built from the series names.
$ go run main.go
$ grep -o '"legend":{[^}]*}' multi.html
"legend":{"show":true}
$ grep -o '"name":"20[0-9][0-9]"' multi.html
"name":"2025"
"name":"2026"
Bar chart
charts.NewBar creates a bar chart. opts.BarData
holds the values, and WithBarChartOpts sets per-series options
such as the bar width.
package main
import (
"os"
"github.com/go-echarts/go-echarts/v2/charts"
"github.com/go-echarts/go-echarts/v2/opts"
)
func main() {
medals := []opts.BarData{{Value: 46}, {Value: 38}, {Value: 29},
{Value: 22}, {Value: 13}, {Value: 11}}
countries := []string{"USA", "China", "UK", "Russia", "South Korea", "Germany"}
bar := charts.NewBar()
bar.SetGlobalOptions(
charts.WithInitializationOpts(opts.Initialization{Theme: "dark"}),
charts.WithTitleOpts(opts.Title{Title: "Olympic gold medals"}),
charts.WithLegendOpts(opts.Legend{Show: opts.Bool(true)}),
)
bar.SetXAxis(countries).
AddSeries("London 2012", medals)
bar.SetSeriesOptions(charts.WithBarChartOpts(opts.BarChart{
BarWidth: "40%",
}))
f, err := os.Create("bar.html")
if err != nil {
panic(err)
}
defer f.Close()
if err := bar.Render(f); err != nil {
panic(err)
}
}
The bar width is a percentage of the available space for one category. Without it, the bars fill the category slot.
$ go run main.go
$ grep -o '"type":"bar"' bar.html
"type":"bar"
$ grep -o '"barWidth":"[^"]*"' bar.html
"barWidth":"40%"
$ grep -o '"data":\[{"value":46}[^]]*\]' bar.html
"data":[{"value":46},{"value":38},{"value":29},{"value":22},{"value":13},{"value":11}]
Pie chart
A pie chart uses named values instead of an x axis. The label formatter can show the name and the percentage of each slice.
package main
import (
"os"
"github.com/go-echarts/go-echarts/v2/charts"
"github.com/go-echarts/go-echarts/v2/opts"
)
func main() {
destinations := []opts.PieData{
{Name: "Croatia", Value: 22},
{Name: "Bohemia", Value: 34},
{Name: "Bulgaria", Value: 18},
{Name: "Spain", Value: 5},
{Name: "Others", Value: 21},
}
pie := charts.NewPie()
pie.SetGlobalOptions(
charts.WithInitializationOpts(opts.Initialization{Theme: "dark"}),
charts.WithTitleOpts(opts.Title{Title: "Popular destinations"}),
)
pie.AddSeries("destinations", destinations)
pie.SetSeriesOptions(charts.WithLabelOpts(opts.Label{
Show: opts.Bool(true),
Formatter: "{b}: {d}%",
}))
f, err := os.Create("pie.html")
if err != nil {
panic(err)
}
defer f.Close()
if err := pie.Render(f); err != nil {
panic(err)
}
}
In the formatter, {b} is the data name and {d} the
percentage. The slice labels take the text color of the dark theme, so they
stay readable on the dark background.
$ go run main.go
$ grep -o '"type":"pie"' pie.html
"type":"pie"
$ grep -o '"formatter":"[^"]*"' pie.html
"formatter":"{b}: {d}%"
$ grep -o '{"name":"Croatia"[^}]*}' pie.html
{"name":"Croatia","value":22}
Scatter chart
A scatter chart plots points against two axes. With a category x axis, the values are the y coordinates; numeric pairs can be used for a true x/y plot.
package main
import (
"os"
"github.com/go-echarts/go-echarts/v2/charts"
"github.com/go-echarts/go-echarts/v2/opts"
)
func main() {
temps := []opts.ScatterData{{Value: -7.3}, {Value: -3.4}, {Value: -5.0},
{Value: -0.9}, {Value: -2.2}, {Value: 4.8}, {Value: 5.1}, {Value: -1.9},
{Value: 0}, {Value: 2.6}}
dates := []string{"Jan 1", "Jan 10", "Jan 12", "Jan 20", "Jan 30",
"Feb 1", "Feb 2", "Feb 5", "Feb 8", "Feb 12"}
scatter := charts.NewScatter()
scatter.SetGlobalOptions(
charts.WithInitializationOpts(opts.Initialization{Theme: "dark"}),
charts.WithTitleOpts(opts.Title{Title: "Temperatures"}),
charts.WithYAxisOpts(opts.YAxis{Name: "°C"}),
)
scatter.SetXAxis(dates).AddSeries("temps", temps)
f, err := os.Create("scatter.html")
if err != nil {
panic(err)
}
defer f.Close()
if err := scatter.Render(f); err != nil {
panic(err)
}
}
The y axis is named with a unit, and the points appear at the given temperatures. A negative value below the zero line is a quick check that the data reached the page.
$ go run main.go
$ grep -o '"type":"scatter"' scatter.html
"type":"scatter"
$ grep -o '{"value":-7.3}' scatter.html
{"value":-7.3}
$ grep -o '"name":"°C"' scatter.html
"name":"°C"
Page title, size and colors
opts.Initialization also controls the HTML page title and the
size of the chart container. A palette can be set with
WithColorsOpts, but in v2.7.3 it is written to the option only when
the theme is white, because themed charts use the colors of their
theme. With a dark chart, a series is colored in AddSeries with
WithLineStyleOpts for the line and
WithItemStyleOpts for the markers.
package main
import (
"os"
"github.com/go-echarts/go-echarts/v2/charts"
"github.com/go-echarts/go-echarts/v2/opts"
)
func main() {
months := []string{"Jan", "Feb", "Mar", "Apr", "May", "Jun"}
a := []opts.LineData{{Value: 120}, {Value: 132}, {Value: 101}, {Value: 134},
{Value: 90}, {Value: 230}}
b := []opts.LineData{{Value: 220}, {Value: 182}, {Value: 191}, {Value: 234},
{Value: 290}, {Value: 330}}
line := charts.NewLine()
line.SetGlobalOptions(
charts.WithInitializationOpts(opts.Initialization{
Theme: "dark",
PageTitle: "Orders dashboard",
Width: "1000px",
Height: "600px",
}),
charts.WithTitleOpts(opts.Title{Title: "Monthly orders"}),
charts.WithLegendOpts(opts.Legend{Show: opts.Bool(true)}),
charts.WithTooltipOpts(opts.Tooltip{Show: opts.Bool(true), Trigger: "axis"}),
)
line.SetXAxis(months).
AddSeries("2025", a,
charts.WithLineStyleOpts(opts.LineStyle{Color: "#e9c46a"}),
charts.WithItemStyleOpts(opts.ItemStyle{Color: "#e9c46a"})).
AddSeries("2026", b,
charts.WithLineStyleOpts(opts.LineStyle{Color: "#2a9d8f"}),
charts.WithItemStyleOpts(opts.ItemStyle{Color: "#2a9d8f"}))
f, err := os.Create("style.html")
if err != nil {
panic(err)
}
defer f.Close()
if err := line.Render(f); err != nil {
panic(err)
}
}
The page title is separate from the chart title: the first is the
<title> of the HTML document, the second is drawn inside the
chart. The default page title is Awesome go-echarts. The colors of
the two lines are set per series, so they remain distinguishable on the dark
background.
$ go run main.go
$ grep -o '<title>[^<]*</title>' style.html
<title>Orders dashboard</title>
$ grep -o 'width:[^;]*;height:[^"]*' style.html
width:1000px;height:600px;
$ grep -o '"lineStyle":{"color":"[^"]*"}' style.html
"lineStyle":{"color":"#e9c46a"}
"lineStyle":{"color":"#2a9d8f"}
$ grep -o '"trigger":"[^"]*"' style.html
"trigger":"axis"
Chart from an HTTP server
Render accepts an http.ResponseWriter as well, so a
handler can build the chart for each request. The response is an HTML page with
the content type set accordingly.
package main
import (
"fmt"
"log"
"math/rand"
"net/http"
"github.com/go-echarts/go-echarts/v2/charts"
"github.com/go-echarts/go-echarts/v2/opts"
)
func randomData(n int) []opts.LineData {
data := make([]opts.LineData, 0, n)
for i := 0; i < n; i++ {
data = append(data, opts.LineData{Value: rand.Intn(300)})
}
return data
}
func handler(w http.ResponseWriter, r *http.Request) {
line := charts.NewLine()
line.SetGlobalOptions(
charts.WithInitializationOpts(opts.Initialization{Theme: "dark"}),
charts.WithTitleOpts(opts.Title{
Title: "Line charts",
Subtitle: "Rendered by the HTTP server",
}),
charts.WithLegendOpts(opts.Legend{Show: opts.Bool(true)}),
)
line.SetXAxis([]string{"Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"}).
AddSeries("Category A", randomData(7)).
AddSeries("Category B", randomData(7))
line.SetSeriesOptions(charts.WithLineChartOpts(opts.LineChart{
Smooth: opts.Bool(true),
}))
if err := line.Render(w); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
}
func main() {
http.HandleFunc("/", handler)
fmt.Println("listening on http://localhost:8096")
log.Fatal(http.ListenAndServe(":8096", nil))
}
The random values are generated on every request, so the chart changes when
the page is reloaded. The handler does not set a content type itself;
Render writes an HTML document.
$ go run main.go
listening on http://localhost:8096
$ curl -s -o /dev/null -w '%{http_code} %{content_type}\n' localhost:8096/
200 text/html; charset=utf-8
$ curl -s localhost:8096/ | grep -o '"title":{[^}]*}'
"title":{"text":"Line charts","subtext":"Rendered by the HTTP server"}
Source
Go echarts - Github page
Apache ECharts - documentation
In this article we have created charts in Go with go-echarts. We produced a line chart, a multi-series chart with a legend and a tooltip, a bar chart, a pie chart and a scatter chart, customized the page title, size, theme and colors, and rendered a chart from an HTTP handler.
Author
List all Go tutorials.