ZetCode

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.

main.go
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).

A line chart with a smooth curve
Figure: A line chart with a smooth curve
$ 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.

main.go
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.

Several series with a legend
Figure: Several series with a legend
$ 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.

main.go
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.

A bar chart with a fixed bar width
Figure: A bar chart with a fixed bar width
$ 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.

main.go
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.

A pie chart with percentage labels
Figure: A pie chart with percentage labels
$ 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.

main.go
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.

A scatter chart with temperatures
Figure: A scatter chart with temperatures
$ 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.

main.go
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.

A dark-theme chart with a custom size and colors
Figure: A dark-theme chart with a custom size and colors
$ 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.

main.go
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.

A chart rendered by the HTTP server
Figure: A chart rendered by the HTTP server
$ 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

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.

List all Go tutorials.