datadash

package module
v0.5.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 6, 2026 License: MIT Imports: 16 Imported by: 0

README

DataDash

CI Release Go Report Card

A high-fidelity, interactive data visualization tool for your terminal.

DataDash turns streams or files of numbers into beautiful, interactive charts — no GUI, no browser, no cloud. Pipe in CSV, TSV, JSON, or hit an HTTP endpoint, and watch your data come to life with sub-cell braille resolution, per-series panels, live statistics, and full keyboard navigation.

datadash streaming


✨ What's New in v0.5

Feature What it does
🎯 Built-in demo mode datadash demo — see what the tool does in 5 seconds, no data required
🔍 Auto-detection CSV, TSV, JSON, NDJSON, pipe-, and space-delimited input — sniffed from the first line
🌐 HTTP polling --url flag turns any JSON endpoint into a live data source
📡 JSON / NDJSON Dot-path field extraction (--field metrics.cpu) with auto-discovery
📐 LTTB downsampling Same algorithm Grafana uses — preserves spikes and valleys when fitting a million points to your screen
📊 Per-series stats Min, Max, Mean, StdDev, P50, P95, P99 — aligned per row, updated live
🎨 Three chart types --chart line (default), --chart bar, --chart sparkline
⌨️ Keyboard navigation h/l pan, +/- zoom, r reset, ? help, q quit
✂️ Smart parsing Currency symbols, percent signs, and thousands separators stripped automatically
🛡️ Robust errors Graceful handling of malformed input — no more panics on bad data
🚀 Cross-platform releases Pre-built binaries for Linux/macOS/Windows × amd64/arm64

Quick Start

# From source
go install github.com/keithknott26/datadash/cmd@latest

# Or download a binary from Releases:
# https://github.com/keithknott26/datadash/releases/latest

# Try it — no data needed
datadash demo

Installation

Pre-built binaries

Download from Releases — Linux, macOS, and Windows binaries for amd64 and arm64 are attached to every release.

Homebrew
brew install keithknott26/tap/datadash
From source
git clone https://github.com/keithknott26/datadash.git
cd datadash
go build -o datadash ./cmd
./datadash demo

Chart Types

DataDash supports three chart types via --chart (or -c):

Type Best for Pan/Zoom Notes
line (default) Continuous time series, trends Sub-cell braille resolution, optional rolling average
bar Recent values at a glance Shows the last 30 values as proportional bars
sparkline Compact dashboards, dense layouts Tiny inline graphs with labels, accumulates over time

Demos

Built-in demos

The fastest way to see DataDash in action — no input required.

datadash demo            # cycles through all generators (~30s each)
datadash demo metrics    # simulated server metrics (cpu, mem, rps, errors)
datadash demo sine       # multiple sine waves
datadash demo walk       # gaussian random walk (looks like a stock chart)
datadash demo anomaly    # baseline signal with random spikes
datadash demo pulse      # periodic pulses with exponential decay

Each demo also works with all chart types:

datadash demo metrics --chart bar
datadash demo metrics --chart sparkline
Streaming data (line chart)

Scrolls right, keeping the most recent data visible:

seq 4000 | awk 'BEGIN{OFS="\t"; print "x"}{x=$1/10; print cos(x); system("sleep 0.01")}' \
  | datadash --label-mode time --scroll

streaming line

Streaming data (bar chart)
seq 4000 | awk 'BEGIN{OFS="\t"; print "x","sin(x)","cos(x)","rand(x)","rand(x)"}{x=$1/10; print x,sin(x),cos(x),rand(x),rand(x); system("sleep 0.02")}' \
  | datadash --chart bar

streaming bar

Streaming data (sparklines)
seq 4000 | awk 'BEGIN{OFS="\t"; print "x","sin(x)","cos(x)","rand(x)","rand(x)"}{x=$1/10; print x,sin(x),cos(x),rand(x),rand(x); system("sleep 0.02")}' \
  | datadash --chart sparkline

streaming sparkline

Tabular data with rolling average
seq 4000 | awk 'BEGIN{OFS="\t"; print "x","sin(x)","cos(x)","rand(x)","rand(x)","rand(x)"}{x=$1/10; print x,sin(x),cos(x),rand(x),rand(x),rand(x); system("sleep 0.02")}' \
  | datadash -a

6-column with average

JSON / NDJSON streams

Auto-discovery of numeric fields:

echo '{"cpu":45.2,"mem":72.1}
{"cpu":51.8,"mem":68.4}
{"cpu":39.1,"mem":75.0}' | datadash

Explicit field extraction with dot paths:

echo '{"host":"web1","metrics":{"cpu":45.2,"mem":72.1}}
{"host":"web1","metrics":{"cpu":51.8,"mem":68.4}}' \
  | datadash -f json --field metrics.cpu --field metrics.mem
HTTP polling

Watch live crypto prices:

datadash \
  --url 'https://api.coingecko.com/api/v3/simple/price?ids=bitcoin,ethereum,solana&vs_currencies=usd' \
  --field bitcoin.usd \
  --field ethereum.usd \
  --field solana.usd \
  --poll 30s

Watch weather data:

datadash \
  --url 'https://api.open-meteo.com/v1/forecast?latitude=37.77&longitude=-122.42&current=temperature_2m,wind_speed_10m,relative_humidity_2m' \
  --field current.temperature_2m \
  --field current.wind_speed_10m \
  --poll 60s

With authentication:

datadash \
  --url https://api.example.com/metrics \
  --header "Authorization: Bearer $TOKEN" \
  --field requests.rate --field errors.rate \
  --poll 5s

Input Methods

DataDash accepts data from four sources, in priority order:

  1. datadash demo — built-in synthetic data
  2. --url <endpoint> — HTTP polling
  3. File argumentdatadash data.txt
  4. Stdincat data.txt | datadash

Data Format

DataDash auto-detects the format from the first line — TSV, CSV, pipe-delimited, JSON, NDJSON, whitespace, or single-column. Header rows are auto-detected and used as series names. Force a format with -f.

Single column (1 graph)
50
60
70
Multi-column (first column as label)
time	RowLabel1	RowLabel2
00:00	50	100
00:01	60	90
00:02	70	80
JSON / NDJSON
{"cpu": 45.2, "mem": 72.1, "errors": 0}
{"cpu": 51.8, "mem": 68.4, "errors": 2}
Decorated values

Currency symbols, percent signs, and thousands separators are stripped automatically:

$1,234.56	99%	€42.00

becomes 1234.56, 99, 42.


⌨️ Keyboard Controls

Key Action
h, Pan left
l, Pan right
+, = Zoom in
- Zoom out
r Reset view
? Toggle help overlay
q, Esc Quit

CLI Reference

Usage:
  datadash [file] [flags]
  datadash [command]

Available Commands:
  demo        Run a built-in demo dataset
  completion  Generate shell completions
  help        Help about any command

Flags:
  -d, --delimiter string         Column delimiter (auto-detected if omitted)
  -f, --format string            Input format: auto, csv, tsv, json, space, single (default "auto")
  -m, --label-mode string        X-axis label source: first, time, none (default "first")
      --field strings            JSON field to extract (dot path, repeatable)

  -c, --chart string             Chart type: line, bar, sparkline (default "line")
      --downsample string        Downsampling: lttb, minmax, none (default "lttb")
      --high-fidelity            Maximize braille sub-cell resolution (default true)

  -s, --scroll                   Scroll mode — keep newest data on screen
  -a, --average-line             Show a rolling average overlay (line chart only)
  -z, --average-seek int         Rolling average window size (default 500)
  -r, --redraw-interval duration Screen redraw interval (default 100ms)
  -l, --seek-interval duration   Input line read interval (default 20ms)

      --url string               Poll an HTTP endpoint instead of reading stdin/file
      --poll duration            HTTP poll interval (default 5s)
      --header strings           HTTP header, 'Key: Value' (repeatable)

      --debug                    Enable debug logging to stderr
  -v, --version                  Print version and exit
Shell completions
# Bash
datadash completion bash | sudo tee /etc/bash_completion.d/datadash

# Zsh
datadash completion zsh > "${fpath[1]}/_datadash"

# Fish
datadash completion fish > ~/.config/fish/completions/datadash.fish

Why DataDash Looks Sharp

Braille canvas. termdash renders lines using Unicode braille characters, giving 2×4 sub-pixel resolution per character cell. A 200×40 terminal effectively has a 400×160 drawing surface.

LTTB downsampling. When you have 100,000 data points and only 200 cells of space, DataDash uses the Largest Triangle Three Buckets algorithm — the same one Grafana and Plotly use — to pick the points that preserve the curve's visual character.


Architecture

datadash/
├── ring.go           # atomic ring buffer with stats + percentiles
├── row.go            # input parsing & format auto-detection
├── uniq.go           # generic deduplication
├── downsample.go     # LTTB & MinMax decimation
├── http_source.go    # HTTP polling reader
├── cmd/
│   ├── datadash.go   # main app: layout, keybinds, refresh loop
│   └── demo.go       # demo subcommand & data generators
└── tools/sampledata/ # bundled sample datasets

The library code is consumable as a Go package:

import "github.com/keithknott26/datadash"

ring := datadash.NewRing(10000)
ring.Push(42.5)
stats := ring.Stats()
sampled := datadash.LTTB(rawData, 1000)

Testing

go test -v -race -coverprofile=coverage.out ./...
go tool cover -html=coverage.out
go test -bench=. -benchmem ./...

Similar Projects

Acknowledgments

Built on termdash. CLI powered by cobra.

License

MIT — see LICENSE.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func FetchOnce added in v0.5.0

func FetchOnce(ctx context.Context, url string, opts ...HTTPOption) ([]byte, error)

FetchOnce performs a single HTTP GET and returns the body. Useful for the `--url` mode when polling isn't needed (e.g. one-shot snapshot).

func FormatAxisValue added in v0.5.0

func FormatAxisValue(v float64, width int) string

FormatAxisValue is like FormatValue but right-pads to a fixed width for aligned Y-axis display.

func FormatValue added in v0.5.0

func FormatValue(v float64) string

FormatValue produces a human-friendly string for a numeric value, using SI suffixes for large numbers and scientific notation for very small ones.

func LTTB added in v0.5.0

func LTTB(data []float64, threshold int) []float64

LTTB implements the Largest Triangle Three Buckets algorithm by Sveinn Steinarsson (2013). Given a series of N points, it returns a series of `threshold` points that visually preserves the original's shape — peaks, dips, and inflection points are kept, while runs of similar values are thinned out. This is what Grafana, Plotly, and most production charting libraries use for time-series downsampling.

If threshold >= len(data) or threshold < 3, the original is returned unchanged.

Runtime: O(n). Allocates one slice of size `threshold`.

func MinMaxDecimate added in v0.5.0

func MinMaxDecimate(data []float64, threshold int) []float64

MinMaxDecimate is a faster but lower-quality alternative to LTTB. It splits the input into `threshold/2` buckets and keeps the min and max of each. Useful when you want extremes preserved but don't care about visual fidelity of the curve between them — e.g. audio waveforms.

func Uniq

func Uniq[T comparable](input []T) []T

Uniq returns a new slice with duplicates removed, preserving the order of first occurrence. Works for any comparable type.

func UniqStrings added in v0.5.0

func UniqStrings(input []string) []string

UniqStrings is a convenience alias for string slices.

Types

type Format added in v0.5.0

type Format int

Format describes the detected or forced input format.

const (
	FormatAuto   Format = iota // sniff from first line
	FormatCSV                  // comma-delimited
	FormatTSV                  // tab-delimited
	FormatSpace                // whitespace-delimited
	FormatJSON                 // newline-delimited JSON objects
	FormatSingle               // one number per line
)

func ParseFormat added in v0.5.0

func ParseFormat(s string) Format

ParseFormat converts a CLI string to a Format constant.

func (Format) String added in v0.5.0

func (f Format) String() string

type HTTPOption added in v0.5.0

type HTTPOption func(*HTTPSource)

HTTPOption configures an HTTPSource.

func HTTPHeader added in v0.5.0

func HTTPHeader(key, value string) HTTPOption

HTTPHeader adds a request header (call multiple times for multiple headers). Useful for Authorization tokens, content negotiation, etc.

func HTTPInterval added in v0.5.0

func HTTPInterval(d time.Duration) HTTPOption

HTTPInterval sets the polling interval. Defaults to 5 seconds.

func HTTPTimeout added in v0.5.0

func HTTPTimeout(d time.Duration) HTTPOption

HTTPTimeout sets the per-request timeout. Defaults to 10 seconds.

type HTTPSource added in v0.5.0

type HTTPSource struct {
	// contains filtered or unexported fields
}

HTTPSource polls an HTTP endpoint at a fixed interval and exposes the responses as an io.Reader stream. Each response body becomes one line in the stream (newline-appended), so a JSON-emitting endpoint looks identical to a streaming NDJSON pipe.

Usage:

src := datadash.NewHTTPSource(ctx, "https://api.example.com/metrics",
    datadash.HTTPInterval(5*time.Second))
reader := datadash.NewReader(src, datadash.WithFormat(datadash.FormatJSON))

func NewHTTPSource added in v0.5.0

func NewHTTPSource(ctx context.Context, url string, opts ...HTTPOption) *HTTPSource

NewHTTPSource starts a polling goroutine and returns a source that can be passed to NewReader. The poll loop terminates when ctx is cancelled or the reader side is closed.

func (*HTTPSource) Close added in v0.5.0

func (s *HTTPSource) Close() error

Close stops the polling goroutine.

func (*HTTPSource) Read added in v0.5.0

func (s *HTTPSource) Read(b []byte) (int, error)

Read implements io.Reader.

type Reader added in v0.5.0

type Reader struct {
	// contains filtered or unexported fields
}

Reader wraps a buffered scanner and produces Rows.

func NewReader added in v0.5.0

func NewReader(r io.Reader, opts ...ReaderOption) *Reader

NewReader creates a streaming row reader. By default it auto-detects the format and delimiter from the first line of input.

func (*Reader) Format added in v0.5.0

func (rd *Reader) Format() Format

Format returns the resolved format (after at least one Read call).

func (*Reader) Headers added in v0.5.0

func (rd *Reader) Headers() []string

Headers returns column headers discovered from the first line. Returns nil if headers haven't been detected yet or the format has no header row.

func (*Reader) Read added in v0.5.0

func (rd *Reader) Read() (Row, error)

Read returns the next Row. Returns io.EOF at end of input.

type ReaderOption added in v0.5.0

type ReaderOption func(*Reader)

ReaderOption configures a Reader.

func WithDelimiter added in v0.5.0

func WithDelimiter(d string) ReaderOption

WithDelimiter overrides the auto-detected delimiter for CSV/TSV.

func WithFormat added in v0.5.0

func WithFormat(f Format) ReaderOption

WithFormat forces a specific input format instead of auto-detecting.

func WithJSONFields added in v0.5.0

func WithJSONFields(fields []string) ReaderOption

WithJSONFields specifies dot-path fields to extract from JSON objects. If empty, all top-level numeric fields are auto-discovered.

func WithLabelMode added in v0.5.0

func WithLabelMode(mode string) ReaderOption

WithLabelMode sets how the X-axis label is derived.

"first" — use the first column as a label (default for multi-column)
"time"  — use wall-clock time as the label
"none"  — no labels

type Ring added in v0.5.0

type Ring struct {
	// contains filtered or unexported fields
}

Ring is a fixed-capacity circular buffer for streaming time-series data. Write operations use atomic counters under a write lock for safety; read operations take a shared lock so multiple consumers can snapshot concurrently without blocking each other.

func NewRing added in v0.5.0

func NewRing(capacity int) *Ring

NewRing allocates a ring buffer that holds at most capacity values. If capacity <= 0 it defaults to 4096.

func (*Ring) Cap added in v0.5.0

func (r *Ring) Cap() int

Cap returns the maximum capacity.

func (*Ring) Last added in v0.5.0

func (r *Ring) Last(n int) []float64

Last returns the most recent n values in chronological order. If n exceeds the current fill, all values are returned.

func (*Ring) Latest added in v0.5.1

func (r *Ring) Latest() (float64, bool)

Latest returns the most recently pushed value and true, or (0, false) if the ring is empty.

func (*Ring) Len added in v0.5.0

func (r *Ring) Len() int

Len returns the number of elements currently in the buffer. Lock-free.

func (*Ring) Position added in v0.5.0

func (r *Ring) Position() int64

Position returns the total number of values ever pushed to the ring (monotonically increasing — never wraps within practical lifetimes). Lock-free.

func (*Ring) Push added in v0.5.0

func (r *Ring) Push(v float64)

Push appends a single value, overwriting the oldest if full.

func (*Ring) PushBatch added in v0.5.0

func (r *Ring) PushBatch(values []float64)

PushBatch appends multiple values in a single lock acquisition.

func (*Ring) Reset added in v0.5.0

func (r *Ring) Reset()

Reset empties the buffer.

func (*Ring) Since added in v0.5.0

func (r *Ring) Since(since int64) (values []float64, pos int64)

Since returns every value written at or after the absolute position `since`, in chronological order, along with the new high-water mark to pass on the next call.

If `since` is older than the oldest value still in the buffer, the result starts from the oldest available value (i.e. `since` is silently advanced to `pos - len`). If no new values are available, returns (nil, currentPos).

This is the right tool for streaming consumers (sparklines, log tails, etc.) that need to receive every value exactly once even as the ring overflows.

func (*Ring) Stats added in v0.5.0

func (r *Ring) Stats() Stats

Stats computes summary statistics over the current buffer contents. Returns a zero Stats if the buffer is empty.

func (*Ring) StatsLast added in v0.5.0

func (r *Ring) StatsLast(n int) Stats

StatsLast computes summary statistics over the most recent n values.

func (*Ring) Values added in v0.5.0

func (r *Ring) Values() []float64

Values returns every element in chronological order (oldest first). The caller owns the returned slice.

type Row

type Row struct {
	Values []float64
	Label  string // optional X-axis label (first column or timestamp)
}

Row is a single parsed record.

type Stats added in v0.5.0

type Stats struct {
	Min, Max, Mean, StdDev float64
	P50, P95, P99          float64
	Count                  int
	Outliers               int // values beyond mean ± 2σ
}

Stats holds summary statistics for the buffer contents.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL