Go-Echarts Interactive Data Visualization

Frontend Engineering intermediate Building with AI ~20 min read

Overview

Go-echarts is a Go library that wraps Apache ECharts, the widely-used JavaScript charting library, providing a pure-Go API for generating interactive charts. Instead of writing JavaScript configuration objects, developers construct charts using Go structs and method chains, and go-echarts renders them as self-contained HTML files that embed the ECharts JavaScript library and the chart’s JSON configuration. The result is interactive, zoomable, tooltipped charts that run in any browser.

The library supports a comprehensive range of chart types: line, bar, scatter, pie, radar, heatmap, treemap, graph (network), tree, sankey, funnel, gauge, geographic maps, and 3D variants. Each chart type is represented by a Go struct with methods for adding data series, configuring axes, setting global options (title, legend, tooltip, toolbox), and customizing visual properties. The chart object’s Render() method writes the complete HTML to an io.Writer.

For Go-heavy backend projects, go-echarts eliminates the need to set up a JavaScript build pipeline just to visualize data. The search platform uses it to generate interactive visualizations of embedding similarity graphs, treemaps of document clusters, and line charts tracking search quality metrics—all produced by the same Go code that computes the underlying data.

Core Concepts

Chart as Go Object

Each chart type (Line, Bar, Scatter, Graph, Tree, Treemap, etc.) is a Go struct created with a constructor like charts.NewLine(). Configuration happens through method chaining: SetGlobalOptions() for chart-level settings (title, legend, tooltip) and AddSeries() for data. The chart object knows how to serialize itself to the JSON format ECharts expects and wraps it in an HTML page.

Global Options vs Series Options

Go-echarts separates configuration into two layers. Global options apply to the entire chart: opts.Title sets the chart title, opts.Legend controls the legend, opts.Tooltip configures hover behavior, opts.Toolbox adds export/zoom controls, and opts.XAxis/opts.YAxis define axes. Series options apply per data series: line style, area fill, symbol shape, label positioning, and animation timing. This mirrors ECharts’ own configuration hierarchy.

Data Items and Generate Functions

Data is passed to charts as slices of opts.LineData, opts.BarData, opts.GraphNode, etc. Each data item struct wraps a value with optional per-point styling (color, symbol, label). Helper functions like generateLineItems() transform application data (e.g., a slice of float64 scores) into the chart’s expected data format. This separation keeps chart construction clean while allowing fine-grained control over individual data points.

Self-Contained HTML Output

When Render() is called, go-echarts produces a complete HTML file that includes a CDN <script> tag for the ECharts JavaScript library and an inline <script> with the chart’s JSON configuration. The output requires no build step, no npm install, and no bundler—open it in a browser and the chart renders. This makes it ideal for reports, dashboards served from Go HTTP handlers, or embedding in documentation.

HTTP Server Integration

Go-echarts charts implement http.Handler through the http.HandlerFunc pattern. A chart can be directly served from a Go HTTP route by calling chart.Render(w) in a handler. For dashboards with multiple charts, the components.Page type composes multiple charts into a single page with configurable layout (vertical stack, flex grid, or custom CSS).

How It Works

Creating a Basic Line Chart

A line chart starts with charts.NewLine(), is configured with global options, and receives data series. The chart renders to any io.Writer—a file, HTTP response, or buffer.

package main

import (
    "os"

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

func generateLineItems(data []float64) []opts.LineData {
    items := make([]opts.LineData, len(data))
    for i, v := range data {
        items[i] = opts.LineData{Value: v}
    }
    return items
}

func main() {
    line := charts.NewLine()

    line.SetGlobalOptions(
        charts.WithTitleOpts(opts.Title{
            Title:    "Search Quality Over Time",
            Subtitle: "NDCG@10 by week",
        }),
        charts.WithTooltipOpts(opts.Tooltip{
            Trigger: "axis",
        }),
        charts.WithToolboxOpts(opts.Toolbox{
            Show: opts.Bool(true),
            Feature: &opts.ToolBoxFeature{
                SaveAsImage: &opts.ToolBoxFeatureSaveAsImage{Show: opts.Bool(true)},
                DataZoom:    &opts.ToolBoxFeatureDataZoom{Show: opts.Bool(true)},
            },
        }),
    )

    weeks := []string{"W1", "W2", "W3", "W4", "W5", "W6", "W7", "W8"}
    ndcg := []float64{0.72, 0.74, 0.73, 0.78, 0.81, 0.80, 0.85, 0.87}

    line.SetXAxis(weeks).
        AddSeries("NDCG@10", generateLineItems(ndcg),
            charts.WithLineChartOpts(opts.LineChart{Smooth: opts.Bool(true)}),
            charts.WithAreaStyleOpts(opts.AreaStyle{Opacity: 0.3}),
        )

    f, _ := os.Create("search_quality.html")
    defer f.Close()
    line.Render(f)
}

Network Graph Visualization

The Graph chart type visualizes relationships between entities—ideal for showing embedding similarity networks where nodes are documents and edges represent cosine similarity above a threshold.

func buildSimilarityGraph(docs []Document, similarities [][]float64, threshold float64) *charts.Graph {
    graph := charts.NewGraph()

    graph.SetGlobalOptions(
        charts.WithTitleOpts(opts.Title{
            Title: "Embedding Similarity Graph",
        }),
        charts.WithTooltipOpts(opts.Tooltip{Show: opts.Bool(true)}),
    )

    // Build nodes
    nodes := make([]opts.GraphNode, len(docs))
    for i, doc := range docs {
        nodes[i] = opts.GraphNode{
            Name:       doc.Title,
            SymbolSize: 20 + int(doc.Relevance*30),
            Category:   doc.Category,
        }
    }

    // Build edges from similarity matrix
    var links []opts.GraphLink
    for i := 0; i < len(docs); i++ {
        for j := i + 1; j < len(docs); j++ {
            if similarities[i][j] > threshold {
                links = append(links, opts.GraphLink{
                    Source: docs[i].Title,
                    Target: docs[j].Title,
                    Value:  similarities[i][j],
                })
            }
        }
    }

    graph.AddSeries("documents", nodes, links,
        charts.WithGraphChartOpts(opts.GraphChart{
            Layout:             "force",
            Roam:               opts.Bool(true),
            FocusNodeAdjacency: opts.Bool(true),
            Force: &opts.GraphForce{
                Repulsion:  100,
                EdgeLength: 50,
            },
        }),
    )

    return graph
}

Treemap for Document Clusters

Treemaps visualize hierarchical data with nested rectangles whose sizes represent a metric. This is effective for showing document cluster distributions by category and subcategory.

func buildClusterTreemap(clusters []Cluster) *charts.TreeMap {
    tm := charts.NewTreeMap()

    tm.SetGlobalOptions(
        charts.WithTitleOpts(opts.Title{
            Title: "Document Clusters by Category",
        }),
    )

    var data []opts.TreeMapNode
    for _, cluster := range clusters {
        children := make([]opts.TreeMapNode, len(cluster.Subclusters))
        for i, sub := range cluster.Subclusters {
            children[i] = opts.TreeMapNode{
                Name:     sub.Name,
                Value:    sub.DocCount,
            }
        }
        data = append(data, opts.TreeMapNode{
            Name:     cluster.Name,
            Children: children,
        })
    }

    tm.AddSeries("clusters", data)
    return tm
}

Multi-Chart Dashboard Page

The components.Page type arranges multiple charts on a single HTML page, enabling dashboard-style layouts served from a single HTTP endpoint.

import "github.com/go-echarts/go-echarts/v2/components"

func dashboardHandler(w http.ResponseWriter, r *http.Request) {
    page := components.NewPage()
    page.PageTitle = "Search Analytics Dashboard"

    page.AddCharts(
        buildQualityLineChart(getWeeklyNDCG()),
        buildQueryVolumeBar(getDailyQueryCounts()),
        buildSimilarityGraph(getTopDocs(), getSimilarityMatrix(), 0.8),
        buildClusterTreemap(getDocClusters()),
    )

    page.Render(w)
}

Practical Applications

In the search platform, go-echarts generates interactive visualizations that help engineers understand embedding quality and search behavior. A force-directed graph shows how documents cluster by semantic similarity, with node sizes reflecting query frequency and edge weights representing cosine similarity. This visualization reveals whether the embedding model groups related documents together and identifies outlier documents that don’t cluster with their expected category.

Line charts track search quality metrics (NDCG, recall, precision) over time as the embedding model is retrained or the index configuration changes. Because go-echarts charts include ECharts’ built-in zoom and data selection tools, engineers can drill into specific time ranges without building a custom frontend. The charts are served from the same Go HTTP server that runs the search API, keeping the monitoring dashboard co-located with the service it monitors.

Tree and treemap charts visualize the hierarchical organization of the training document corpus. Category sizes show where documentation coverage is deep versus thin, helping prioritize which topics to expand. The interactive drill-down capability of treemaps lets users explore from high-level categories down to individual documents without page navigation.

Implementation Patterns

Dynamic Chart Updates via HTTP

Serve charts from Go HTTP handlers that query live data on each request. The chart HTML is regenerated with current data, and the browser’s ECharts instance renders the latest state.

http.HandleFunc("/charts/quality", func(w http.ResponseWriter, r *http.Request) {
    timeRange := r.URL.Query().Get("range") // "7d", "30d", "90d"
    data := fetchQualityMetrics(timeRange)

    line := charts.NewLine()
    line.SetGlobalOptions(
        charts.WithTitleOpts(opts.Title{
            Title: fmt.Sprintf("Search Quality (%s)", timeRange),
        }),
        charts.WithInitializationOpts(opts.Initialization{
            Width:  "900px",
            Height: "400px",
        }),
    )

    line.SetXAxis(data.Dates).
        AddSeries("NDCG@10", generateLineItems(data.NDCG)).
        AddSeries("Recall@10", generateLineItems(data.Recall))

    line.Render(w)
})

Custom Themes and Styling

Go-echarts supports ECharts themes for consistent visual branding. Register a custom theme JSON or use built-in themes like "dark", "vintage", or "westeros".

line := charts.NewLine()
line.SetGlobalOptions(
    charts.WithInitializationOpts(opts.Initialization{
        Theme: "dark",
    }),
    charts.WithColorsOpts(opts.Colors{"#6c63ff", "#00d4aa", "#ff6b8a", "#ffd93d"}),
)

Common Pitfalls

Further Reading