plot

package module
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Oct 19, 2026 License: MIT Imports: 6 Imported by: 0

README

plot

Go Reference

tuios.dev/plot draws charts as terminal cells. It has eight chart kinds: bar (horizontal and vertical), line, area, sparkline, gauge, histogram, heatmap and pie (a donut or a full disc).

A chart draws into a grid of the size you give it. Each cell holds one grapheme and a role, such as series1, mute or track. Your program maps each role to a colour at the colour depth of the terminal. The package holds no colour and writes no escape sequence.

bar := plot.Bar{
	Values:     []float64{6.2, 4.8, 3.1},
	Labels:     []string{"session", "app", "vt"},
	Horizontal: true,
}
g := bar.Draw(30, 3, plot.Blocks)
fmt.Println(g.String())
session ██████████████████ 6.2
app     █████████████▉     4.8
vt      █████████          3.1

Modes

A mode sets the glyphs a chart can use. Use the best mode that the terminal and its font can show.

Mode Lines and areas Bars, sparklines, gauges
Braille Braille dots, 2 by 4 per cell Eighth blocks
Blocks Eighth blocks Eighth blocks
ASCII * and : #, - and [ ]

Heatmaps use whole cells, or half blocks when the rows do not fit. A pie is a disc of half-block pixels. In ASCII each series of a line chart, each slice of a pie and each heat level has its own mark, so they stay apart without colour.

Axes and legends

  • Columns, lines, areas and histograms can have a y axis with round tick labels, a base line, and the first, middle and last x label under it.
  • Columns whose values all sit far above zero start from a round base under the smallest value. The axis labels that base.
  • Horizontal bars keep a value column at the right that never clips. Labels give way first.
  • Line.Legend and Pie.Legend return the items for a legend, and DrawLegend draws them.

A cell can also have a background role, Cell.Bg. Half blocks use it to show two pixels in one cell. The heat roles heat1 to heat5 are a ramp from low to high that the host derives.

Rules

  • Every chart is total. NaN and infinite values count as missing. Empty data draws an empty grid.
  • A grid is always the size you ask for, and each row is exactly that wide.
  • Sizes that a caller could pass unchecked are bounded: a grid is at most 4096 cells a side, a histogram has at most one bin per cell and 1000 bins, a line draws at most 64 series, and a pie draws 12 slices, the rest folded into one called "other".
  • Labels are cut with an ellipsis to fit. Control characters are dropped and invalid UTF-8 shows as ?.
  • A line chart with more values than cells is reduced with largest triangle three buckets, which keeps the peaks.

Widths come from github.com/charmbracelet/x/ansi, the same width functions that tuios uses. The package imports nothing else outside the standard library.

tuios.dev/loom uses this package to draw the TML chart components.

Documentation

Overview

Package plot draws charts as terminal cells: bar, line, area, sparkline, gauge and histogram.

A chart draws into a Grid of a given width and height. Each cell holds one grapheme and a Role, the name of a theme colour such as "series1" or "mute". The host maps roles to its own colours at its own colour depth, so the package holds no colour and writes no escape sequence.

A Mode picks the glyphs: Braille uses braille dots for lines and areas and eighth blocks for bars, Blocks uses only block elements, and ASCII uses printable ASCII only. Pick the best mode the terminal and its font can show.

Every chart is total. NaN and infinite values count as missing, empty data draws an empty grid, and a grid is always exactly the size asked for.

Example
package main

import (
	"fmt"

	"tuios.dev/plot"
)

func main() {
	bar := plot.Bar{
		Values:     []float64{6.2, 4.8, 3.1},
		Labels:     []string{"session", "app", "vt"},
		Horizontal: true,
	}
	g := bar.Draw(30, 3, plot.Blocks)
	fmt.Println(g.String())
	// The role of each cell names a theme colour for the host to map.
	fmt.Println(g.At(8, 0).Role)
}
Output:
session ██████████████████ 6.2
app     █████████████▉     4.8
vt      █████████          3.1
series1

Index

Examples

Constants

View Source
const MaxSide = 4096

MaxSide is the largest width or height of a grid. A larger size is cut to it, so a caller cannot make a chart allocate without bound.

Variables

This section is empty.

Functions

func Compact

func Compact(v float64) string

Compact formats v in at most about five cells: 412, 6.2, 0.35, 12k, 3.4M.

func Downsample

func Downsample(vals []float64, n int) []float64

Downsample reduces vals to at most n points with largest triangle three buckets, which keeps the peaks a plain average would flatten. Missing values are dropped first. It returns vals itself when it already fits.

func DrawLegend

func DrawLegend(g *Grid, x, y int, items []LegendItem, m Mode) int

DrawLegend writes legend items on row y from x, each a coloured mark and its label, and returns the column after the last. It stops before an item that would pass the right edge.

func LegendWidth

func LegendWidth(items []LegendItem) int

LegendWidth returns the cells DrawLegend needs for items.

func Percent

func Percent(f float64) string

Percent formats a fraction as a percentage with at most one decimal: 0.988 is 98.8%, 1 is 100%.

Types

type Bar

type Bar struct {
	Values []float64
	Labels []string // one per value, or none
	Role   Role     // the bars; Series1 when empty
	Roles  []Role   // per bar, over Role
	// Horizontal draws one bar per row: the label at the left, the value
	// at the right. Otherwise each value is a column.
	Horizontal bool
	// Min and Max fix the range. Both 0 means automatic: from zero, or, for
	// columns whose values all sit far above zero, from a round base under
	// the smallest value. The axis labels the base, so the chart says
	// where it starts.
	Min, Max float64
	// Format writes a value: the value column of horizontal bars and the
	// tick labels of columns. Nil means Compact.
	Format func(float64) string
	// Guide fills the rest of a horizontal bar's row with a dotted line.
	Guide bool
	// Axis draws tick labels at the left of columns, a base line, and the
	// first, middle and last label under it.
	Axis bool
}

Bar is a bar chart: one bar per value. Negative values draw as the base.

func (Bar) Draw

func (b Bar) Draw(w, h int, m Mode) *Grid

Draw draws the chart into a w by h grid.

type Cell

type Cell struct {
	Text  string
	Width uint8
	Role  Role
	Bg    Role
	Bold  bool
}

Cell is one cell of a Grid. Text is one grapheme, or "" for a blank cell. Width is the grapheme's width in cells. A wide grapheme takes two cells: the second has Width 0 and no Text. Role colours the text. Bg, when set, colours the cell's background: a half block uses both to show two pixels in one cell.

type Gauge

type Gauge struct {
	Value float64
	Max   float64 // 0 means 1
	Role  Role    // the filled part; Series1 when empty
	// Label replaces the percentage at the right. "-" shows nothing.
	Label string
	Style GaugeStyle
}

Gauge is a value against a maximum, on one row, with the percentage or a label at the right.

func (Gauge) Draw

func (gg Gauge) Draw(w, h int, m Mode) *Grid

Draw draws the gauge into a w by h grid, on the middle row.

func (Gauge) Fraction

func (gg Gauge) Fraction() float64

Fraction returns Value / Max, clamped to [0, 1]. A missing value is 0.

type GaugeStyle

type GaugeStyle uint8

GaugeStyle is how a gauge draws its track.

const (
	GaugeLine   GaugeStyle = iota // a heavy line: ━━━━━━━━━━
	GaugeBlocks                   // eighth blocks on a shaded track: ████▌░░░
)

The gauge styles.

type Grid

type Grid struct {
	W, H  int
	Cells []Cell // row-major
}

Grid is a chart's output: H rows of W cells.

func NewGrid

func NewGrid(w, h int) *Grid

NewGrid returns a blank grid. A negative size counts as 0, and a size past MaxSide counts as MaxSide.

func (*Grid) At

func (g *Grid) At(x, y int) Cell

At returns the cell at x, y, or a blank cell outside the grid.

func (*Grid) String

func (g *Grid) String() string

String returns the grid as text, one line per row, with trailing blanks cut.

func (*Grid) Text

func (g *Grid) Text(x, y int, s string, role Role, bold bool) int

Text writes s from x on row y and returns the column after the last cell written. It stops at the right edge: a wide grapheme that does not fit is left out. Control characters are dropped.

func (*Grid) TextRight

func (g *Grid) TextRight(right, y int, s string, role Role, bold bool)

TextRight writes s so that it ends at column right, exclusive.

type Heatmap

type Heatmap struct {
	Values    [][]float64 // rows of columns; NaN is an empty cell
	RowLabels []string
	ColLabels []string
	// Min and Max fix the range. Both 0 means the range of the values.
	Min, Max float64
	Format   func(float64) string
}

Heatmap is a grid of values drawn as shades of the Heat1 to Heat5 roles, with row labels at the left, the first and last column labels under it, and a scale.

func (Heatmap) Draw

func (hm Heatmap) Draw(w, h int, m Mode) *Grid

Draw draws the heatmap into a w by h grid. A row of values takes one row of cells when they fit, and half a row each (upper and lower half blocks) when they do not.

type Histogram

type Histogram struct {
	Values []float64
	Bins   int  // 0 means one bin per two cells, at most 20
	Role   Role // Series1 when empty
	// Format writes the values at the ends of the range. Nil means Compact.
	Format func(float64) string
}

Histogram counts values into equal bins and draws the counts as columns, with the counts at the left and the range of each end under them.

func (Histogram) Counts

func (hg Histogram) Counts(bins int) (counts []float64, lo, hi float64)

Counts returns the bins' counts, and the range the bins cover.

func (Histogram) Draw

func (hg Histogram) Draw(w, h int, m Mode) *Grid

Draw draws the histogram into a w by h grid.

type LegendItem

type LegendItem struct {
	Label string
	Role  Role
}

LegendItem is one entry of a chart's legend.

type Line

type Line struct {
	Series []Series
	Fill   bool // fill below the line: an area chart
	// Axis draws tick labels at the left, a base line, and the first,
	// middle and last of Labels under it.
	Axis bool
	// Labels name the x positions, one per value of the longest series.
	Labels []string
	// Min and Max fix the range. Both 0 means a round range around the
	// values, with 0 included for an area.
	Min, Max float64
	Format   func(float64) string
}

Line is a line chart, or an area chart when Fill is set. The values of each series are evenly spaced across the width.

func (Line) Draw

func (l Line) Draw(w, h int, m Mode) *Grid

Draw draws the chart into a w by h grid.

func (Line) Legend

func (l Line) Legend() []LegendItem

Legend returns the series names and roles, for a legend the host draws beside the title: the series Draw draws, at most 64.

type Mode

type Mode uint8

Mode is the set of glyphs a chart may draw with.

const (
	Braille Mode = iota // braille dots and block elements
	Blocks              // block elements only
	ASCII               // printable ASCII only
)

The modes, best first.

func (Mode) String

func (m Mode) String() string

type Pie

type Pie struct {
	Values []float64 // negative and missing values count as 0
	Labels []string
	Roles  []Role // per slice; SeriesRole(i) when empty
	// Donut leaves a hole in the middle.
	Donut bool
	// Aspect is a cell's height over its width. 0 means 2.2, a common
	// terminal font. The disc is round when it matches the font.
	Aspect float64
}

Pie shows shares of a whole as a ring of cells (a donut), or a full disc, with a legend at the right: each slice's mark, label and share. The disc is drawn in half blocks, two square pixels to a cell, each coloured by the slice that covers most of it, starting at twelve o'clock and going clockwise.

func (Pie) Draw

func (p Pie) Draw(w, h int, m Mode) *Grid

Draw draws the pie into a w by h grid: the disc at the left, the legend at the right when there is room.

func (Pie) Legend

func (p Pie) Legend() []LegendItem

Legend returns the slices' labels with their shares, and their roles.

type Role

type Role string

Role names a theme colour. The names are the TML roles, plus Track for the empty part of a gauge or a bar.

const (
	None         Role = ""
	Fg           Role = "fg"
	Dim          Role = "dim"
	Mute         Role = "mute"
	Edge         Role = "edge"
	Track        Role = "track"
	Accent       Role = "accent"
	AccentBright Role = "accent-bright"
	Success      Role = "success"
	Warning      Role = "warning"
	Error        Role = "error"
	Info         Role = "info"
	Series1      Role = "series1"
	Series2      Role = "series2"
	Series3      Role = "series3"
	Series4      Role = "series4"
	Series5      Role = "series5"
	Series6      Role = "series6"
	// Heat1 to Heat5 are a ramp from low to high for a heatmap. They are
	// not TML roles: a host derives them, such as from the ground toward
	// the accent.
	Heat1 Role = "heat1"
	Heat2 Role = "heat2"
	Heat3 Role = "heat3"
	Heat4 Role = "heat4"
	Heat5 Role = "heat5"
)

The roles a chart draws with.

func SeriesRole

func SeriesRole(i int) Role

SeriesRole returns the role of series i, counting from 0. It cycles after six.

type Series

type Series struct {
	Name   string
	Values []float64 // evenly spaced; a missing value breaks the line
	Role   Role      // SeriesRole(i) when empty
}

Series is one line of a Line chart.

type Sparkline

type Sparkline struct {
	Values []float64
	Role   Role // Series1 when empty
	// Min and Max fix the range. Both 0 means the range of the values.
	Min, Max float64
}

Sparkline is a one-row trend: one cell per value, the height in eighths.

Example
package main

import (
	"fmt"

	"tuios.dev/plot"
)

func main() {
	g := plot.Sparkline{Values: []float64{2, 5, 3, 8, 4, 9}}.Draw(6, 1, plot.Braille)
	fmt.Println(g.String())
	g = plot.Sparkline{Values: []float64{2, 5, 3, 8, 4, 9}}.Draw(6, 1, plot.ASCII)
	fmt.Println(g.String())
}
Output:
▁▄▂▇▃█
_~.*-#

func (Sparkline) Draw

func (s Sparkline) Draw(w, h int, m Mode) *Grid

Draw draws the sparkline into a w by h grid. On one row each value is an eighth block. On more rows it is a column chart with no axis. More values than cells are averaged into buckets; fewer take fewer cells, from the left.

Jump to

Keyboard shortcuts

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