Back to Articles

ASCII Graphs in Go: How guptarohit/asciigraph Renders Data Visualizations Without Dependencies

[ View on GitHub ]

ASCII Graphs in Go: How guptarohit/asciigraph Renders Data Visualizations Without Dependencies

Hook

The best data visualization library for your production logs might not be a charting library at all—it's a 500-line Go package that renders graphs using nothing but Unicode box-drawing characters.

Context

Developers building CLI tools, monitoring systems, or server applications face a persistent challenge: how do you visualize data when there's no GUI? Traditional charting libraries require web servers, rendering engines, or GUI frameworks—massive overhead when you just want to show CPU usage trends in a terminal or plot response times in log output. For years, the go-to solution was piping data to external tools like gnuplot or building crude bar charts with asterisks and hyphens. These approaches either introduced heavy dependencies or produced barely-readable output.

The asciigraph library by Rohit Gupta solves this elegantly by embracing the constraints of terminal environments. Instead of fighting against text-only output, it leverages Unicode's box-drawing characters (╭╮╯╰─│┤┼) to create surprisingly readable line graphs that render anywhere—SSH sessions, Docker logs, CI/CD pipelines, or system monitoring dashboards. With zero external dependencies and a simple API, it's become the de facto standard for terminal-based data visualization in Go, accumulating over 3,000 GitHub stars since its release.

Technical Insight

Height, Width, Colors

Caption, Offset

Input: float64 slice

Calculate Min/Max Range

Normalize Values to Grid

Build 2D Rune Array

Select Box-Drawing Chars

Render Y-Axis Labels

Output: ASCII String

Functional Options

CLI stdin

Terminal Display

System architecture — auto-generated

At its core, asciigraph solves a deceptively complex problem: mapping arbitrary floating-point data into a fixed-width, fixed-height grid of characters while maintaining readability. The architecture is remarkably clean—the entire library is built around a single Plot() function that accepts a []float64 slice and returns a string, with customization handled through functional options.

Here's a basic example that demonstrates the API:

package main

import (
    "fmt"
    "github.com/guptarohit/asciigraph"
)

func main() {
    data := []float64{3, 4, 9, 6, 2, 4, 5, 8, 5, 10, 2, 7, 2, 5, 6}
    graph := asciigraph.Plot(data)
    fmt.Println(graph)
}

This produces:

 10.00 ┤        ╭╮
  8.00 ┤      ╭╯╰╮
  6.00 ┤ ╭╮  ╭╯  ╰╮ ╭
  4.00 ┤╭╯╰╮╭╯    ╰─╯
  2.00 ┼╯  ╰╯

The magic happens in how asciigraph handles normalization and scaling. The library first calculates the data range (min/max values), then maps each point to a Y-coordinate within the specified height. The interesting architectural decision is in the rendering pass: rather than plotting points sequentially, it builds the entire graph as a 2D array of runes, allowing it to handle overlapping points and smooth curves by choosing the appropriate box-drawing character for each cell.

The functional options pattern provides extensive customization without bloating the main API:

graph := asciigraph.Plot(data,
    asciigraph.Height(15),
    asciigraph.Width(60),
    asciigraph.Caption("Response Time (ms)"),
    asciigraph.Offset(10),
)

For real-world applications, the multi-series support is particularly valuable. Here's how you'd plot multiple metrics on the same graph:

package main

import (
    "fmt"
    "github.com/guptarohit/asciigraph"
)

func main() {
    cpuUsage := []float64{20, 25, 30, 28, 35, 40, 38}
    memoryUsage := []float64{45, 47, 50, 48, 52, 55, 53}
    
    graph := asciigraph.PlotMany(
        [][]float64{cpuUsage, memoryUsage},
        asciigraph.Height(10),
        asciigraph.Caption("System Metrics"),
        asciigraph.SeriesColors(
            asciigraph.Red,
            asciigraph.Blue,
        ),
    )
    fmt.Println(graph)
}

The color support uses ANSI escape codes intelligently—the library detects when output is being piped or redirected and automatically strips color codes to prevent garbage characters in log files. This attention to real-world usage patterns shows throughout the codebase.

One particularly clever design choice is the axis formatting system. The library provides a LabelFormatter option that accepts a function, allowing you to customize how Y-axis values are displayed:

graph := asciigraph.Plot(data,
    asciigraph.Precision(0),
    asciigraph.LabelFormatter(func(v float64) string {
        return fmt.Sprintf("$%.0f", v)
    }),
)

The CLI tool is built as a thin wrapper around the library, but adds streaming capabilities that make it invaluable for monitoring. You can pipe continuous data through it with configurable frame rates:

ping -i 0.2 google.com | grep -oP 'time=\K[0-9.]+' | asciigraph -r -h 10 -w 40

This command visualizes ping latency in real-time, buffering values and redrawing the graph at regular intervals—perfect for SSH sessions where you need quick visual feedback without installing monitoring agents.

Gotcha

The most significant limitation is precision—you're working with character-cell resolution, which means data points get rounded to the nearest row. For datasets with subtle variations or where exact values matter, ASCII rendering simply can't provide the fidelity needed. If your graph needs 100 data points on a 40-character-wide terminal, you'll see aliasing and loss of detail that might obscure important trends.

Color support is another pain point. While the library handles ANSI codes well, you're at the mercy of terminal configurations. Some environments strip colors, others render them incorrectly, and tmux/screen sessions can introduce rendering artifacts. The library does provide asciigraph.DisableColors() as an escape hatch, but then you lose the ability to distinguish multiple series easily. There's also no interactive features—you can't zoom, pan, or hover for exact values like you would with web-based charting libraries. The graph is static text, which means it's read-only output. Finally, the library only supports line graphs. If you need bar charts, histograms, scatter plots, or box plots, you'll need to look elsewhere or combine asciigraph with other tools.

Verdict

Use if: You're building CLI tools, system utilities, or server applications where terminal output is the primary interface and you need quick data visualization without GUI dependencies. It's perfect for monitoring dashboards in SSH sessions, adding visual feedback to build scripts, embedding graphs in log output, or creating lightweight DevOps tools where installation complexity matters. The real-time streaming capability makes it especially valuable for live monitoring scenarios. Skip if: You need interactive charts, high-precision visualization, multiple chart types beyond line graphs, or you're building applications where proper graphical libraries are already in the stack. For web applications, use Chart.js or D3. For scientific computing with precision requirements, use matplotlib or R. ASCII graphs trade fidelity for simplicity—only make that trade when terminal constraints demand it.

// ADD TO YOUR README
[![Featured on Starlog](https://starlog.is/api/badge/developer-tools/guptarohit-asciigraph.svg)](https://starlog.is/api/badge-click/developer-tools/guptarohit-asciigraph)