plotext backend
The plotext backend renders a maxplotlib canvas directly in the terminal. That makes it a good fit for SSH sessions, CLI workflows, quick diagnostics, and situations where you want the same Canvas API without a GUI browser or desktop window.
What this tutorial covers
Section titled “What this tutorial covers”| Area | Status |
|---|---|
plot() / line graphs | ✅ |
scatter() | ✅ |
bar() | ✅ |
fill_between() to a baseline | ✅ |
fill_between() between two curves | ✅ |
errorbar() | ✅ |
axhline() / axvline() / hlines() / vlines() | ✅ |
text() / annotate() | ✅ |
| Titles, labels, captions, limits, ticks, grid, log/symlog scales | ✅ |
set_aspect() | ✅ |
| Layers | ✅ |
| Multi-subplot canvases | ✅ |
add_imshow() matrix-style rendering | ✅ |
add_colorbar() note-style summary | ✅ |
| Generic matplotlib patch geometry | ✅ (best effort) |
| Terminal animation redraw loop | ✅ |
The goal is not pixel-identical matplotlib output. The goal is a faithful terminal representation of the same plot intent.
from pathlib import Path
import matplotlib.patches as mpatchesimport numpy as np
from maxplotlib import Canvas1 · Figure lifecycle: plot(), build(), show(), and savefig()
Section titled “1 · Figure lifecycle: plot(), build(), show(), and savefig()”Use canvas.render(backend="plotext") when you want a reusable terminal figure object. The returned object supports:
.build()to get the rendered terminal plot as a string.show()to print it immediately.savefig(path)to write the terminal output to a text file
Use canvas.show(backend="plotext") when you just want direct terminal output.
x = np.linspace(0, 2 * np.pi, 100)
canvas = Canvas()canvas.plot(x, np.sin(x), color="cyan", label="sin(x)")canvas.set_title("Demo")canvas.set_xlabel("x")canvas.set_ylabel("y")
terminal_fig = canvas.render(backend="plotext")preview = terminal_fig.build(keep_colors=False)print(preview)
# terminal_fig.show()# canvas.show(backend="plotext") Demo ┌─────────────────────────────────────────────────────────────────────────┐ 1.00┤┌──────────┐ ▄▗▄▗▖▄▗▖ │ ││ │▞▝▘ ▀▗▖ │ ││ ▚ sin(x) │ ▝▝▖ │ ││ │ ▚▗ │ 0.50┤└──────────┘ ▘▖ │ │ ▗▝ ▝▗ │ │ ▗▘▘ ▚ │ │ ▞ ▝▖ │ 0.00┤▗ ▚ ▖│ │ ▝▖ ▞ │ │ ▚ ▗▗▘ │ │ ▘▖ ▖▘ │-0.50┤ ▝▗ ▗▝ │ │ ▘▚ ▖▘ │ │ ▝▖▖ ▖▀ │ │ ▝▘▄ ▗▖▞▝ │-1.00┤ ▝▘▀▝▘▀▘▀ │ └┬───────────┬───────────┬───────────┬───────────┬───────────┬───────────┬┘ 0.0 1.0 2.1 3.1 4.2 5.2 6.3y x2 · Line plots
Section titled “2 · Line plots”Line plots are the natural fit for plotext. Labels, markers, grid lines, axis titles, and legends all carry over cleanly.
x = np.linspace(0, 2 * np.pi, 120)
canvas = Canvas()canvas.plot(x, np.sin(x), color="cyan", label="sin(x)")canvas.plot(x, np.cos(x), color="yellow", label="cos(x)", marker="dot")canvas.plot(x, np.sin(2 * x), color="green", label="sin(2x)")canvas.set_title("Multiple terminal lines")canvas.set_xlabel("x")canvas.set_ylabel("value")canvas.set_grid(True)canvas.set_legend(True)
print(canvas.render(backend="plotext").build(keep_colors=False)) Multiple terminal lines ┌┬───────────┬───────────┬───────────┬───────────┬───────────┬───────────┬┐ 1.00┼┌───────────┐──▄▄▖▄▄▖▄▖─┼───────────┼───────▄▄▖▖┼───────────┼───────•••••┤ ││ │▀▘ ▝▘▚▖ │ ▞▘ ▝▚ sin(x) │ •••• ││ ││ ▚ sin(x) │▚ │▝▝▄ │ ▞ │▝▖ │ ••• ││ ││ │ ▘▖ │ ▚▗ │ ▘ │ cos(x) │•• ││ 0.50┼│ • cos(x) │•─▗────────┼─────▚─────┼──▞────────┼─sin(2x)──•••──────────┼┤ ││ │•• ▘ │ ▀▗ │ ▗ │ ▝▗ ••│ ││ ││ ▚ sin(2x) │ •▝ │ ▚ │ ▘ │ ▖ •• │ ││ ││ │ •▘▖ │ ▀▗│▘ │ ▝ •• │ ││ 0.00┼└───────────┘─────▗─────┼───────────█───────────┼─────▘─────┼──────────▗▖┤ ││ │ ▖• │ ▗│▘▄ │ ••▝▗ │ ▗▀││ ││ │ ▝ • │ ▗ │ ▚ │ • ▖ │ ▗▖▚▘││ ││ │ ▘▖••│ ▘ │ ▘▄ │•• ▗ │ ▗▘ ▖ ││-0.50┼┼───────────┼────────▗──••───────▞──┼─────▚────••────────▘──┼────▗▖▘─▖──┼┤ ││ │ ▘ │••• ▗ │ ▘▚•••│ ▝▗ │ ▄▘ ▞ ││ ││ │ ▝▖│ •• ▞ │ ••▖▖│ ▚│ ▞▘ ▝ ││ ││ │ ▚▖ ▗▞• │ ••• ▝▚▗▖ ▗▄▘▛ ▗▘▘ ││-1.00┼┼───────────┼───────────┼▝▝▀▀───•••••••••───────┼─▝▀▝▀▀▝▀▀──┼▝▀▘▀▘──────┼┤ └┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼┘ 0.0 1.0 2.1 3.1 4.2 5.2 6.3value x3 · Scatter plots
Section titled “3 · Scatter plots”Scatter traces use plotext.scatter(...) under the hood. They combine well with a line on the same axes.
rng = np.random.default_rng(42)x = np.linspace(0, 8, 60)samples_x = np.linspace(0, 8, 15)samples_y = np.sin(samples_x) + rng.normal(0, 0.15, len(samples_x))
canvas = Canvas()canvas.plot(x, np.sin(x), color="white", label="sin(x)")canvas.scatter(samples_x, samples_y, color="red", marker="x", label="samples")canvas.set_title("Scatter + line")canvas.set_xlabel("x")canvas.set_ylabel("y")canvas.set_legend(True)
print(canvas.render(backend="plotext").build(keep_colors=False)) Scatter + line ┌──────────────────────────────────────────────────────────────────────────┐ 1.1┤┌───────────┐ x x │ ││ │ ▘▘▗▗ sin(x) ▗ ▖▘x│ ││ ▚ sin(x) │ ▘▖ ▘▝ │ ││ │ ▝ samples ▗ ▘ │ 0.6┤│ x samples │ ▝ ▖ x │ ││ │ x ▖ ▘ │ │└───────────┘ ▖ ▘ │ │ ▗ ▗ x ▝ │ 0.0┤x x ▗ ▝ │ │ ▖ ▘ │ │ x ▘ │ │ ▗ x ▝ │-0.5┤ ▗ ▖▝ │ │ ▘ ▖ │ │ ▘x ▝ │ │ ▝ ▖ ▖▗▝ │-1.0┤ ▘x▝ ▘ x │ └┬───────────┬───────────┬────────────┬───────────┬───────────┬───────────┬┘ 0.0 1.3 2.7 4.0 5.3 6.7 8.0y x4 · Bar charts
Section titled “4 · Bar charts”Bars are supported too, so summary views and simple dashboards work well in the terminal.
bins = np.arange(5)values = np.array([4.0, 6.5, 3.2, 7.4, 5.8])
canvas = Canvas()canvas.bar(bins, values, color="green", label="count")canvas.scatter(bins, values, color="yellow", label="sample mean")canvas.set_title("Bar + scatter overlay")canvas.set_xlabel("bin")canvas.set_ylabel("value")canvas.set_legend(True)
print(canvas.render(backend="plotext").build(keep_colors=False)) Bar + scatter overlay ┌───────────────────────────────────────────────────────────────────────────┐7.4┤┌───────────────┐ ██████▗███████ │ ││ │ ███count██████ │ ││ █ count │█████▘██████ ██████████████ │ ││ │████████████ ███sample mean ██████▖██████│5.6┤│ ▚ sample mean │████████████ ██████████████ █████████████│ ││ │████████████ ██████████████ █████████████│ │└───────────────┘████████████ ██████████████ █████████████│ │██████▗██████ ██████████████ ██████████████ █████████████│3.7┤█████████████ ██████████████ ██████████████ █████████████│ │█████████████ ██████████████ ██████▗██████ ██████████████ █████████████│ │█████████████ ██████████████ █████████████ ██████████████ █████████████│ │█████████████ ██████████████ █████████████ ██████████████ █████████████│1.9┤█████████████ ██████████████ █████████████ ██████████████ █████████████│ │█████████████ ██████████████ █████████████ ██████████████ █████████████│ │█████████████ ██████████████ █████████████ ██████████████ █████████████│ │█████████████ ██████████████ █████████████ ██████████████ █████████████│0.0┤█████████████ ██████████████ █████████████ ██████████████ █████████████│ └──────┬───────────────┬──────────────┬──────────────┬───────────────┬──────┘ 0 1 2 3 4value bin5 · Filled regions
Section titled “5 · Filled regions”The backend supports both common fill_between(...) shapes:
- Filling down to a scalar baseline
- Filling the region between two full curves
x = np.linspace(0, 5, 100)
canvas = Canvas()canvas.fill_between( x, np.exp(-0.5 * x) * np.sin(3 * x) + 1.0, 0.0, color="cyan", label="signal envelope",)canvas.set_title("fill_between() to a scalar baseline")canvas.set_xlabel("x")canvas.set_ylabel("amplitude")canvas.set_legend(True)
print(canvas.render(backend="plotext").build(keep_colors=False)) fill_between() to a scalar baseline ┌──────────────────────────────────────────────────────────────────────────┐1.78┤┌───────────────────┐ │ ││ │ │ ││ ▚ signal envelope │ signal envelope │ ││ │ │1.47┤└───────────────────┘ │ │ ▝ │ │ ▗ ▘ │ │ ▝ ▖▞▝▘▀▝▘▖ │1.16┤ ▖ ▝ ▗▝ ▝▝▖ │ │ ▖ ▖ ▚ ▗▗▖▀▝▘▀▝▘▄▖│ │▗ ▗ ▗▘ ▘▚ ▗▗▘▘ │ │ ▗ ▗ ▝▖▖ ▖▄▘▘ │0.85┤ ▖ ▘ ▝▝▘▄▗▖▀▝ │ │ ▖ ▗▝ │ │ ▗ ▖▘ │ │ ▝▖ ▗▝ │0.54┤ ▀▝▘▘ │ └┬───────────┬───────────┬────────────┬───────────┬───────────┬───────────┬┘ 0.0 0.8 1.7 2.5 3.3 4.2 5.0amplitude xx = np.linspace(0, 4, 100)upper = np.sin(x) + 1.8lower = np.cos(x) + 0.8
canvas = Canvas()canvas.fill_between(x, upper, lower, color="blue", label="between curves")canvas.plot(x, upper, color="white", label="upper")canvas.plot(x, lower, color="yellow", label="lower")canvas.set_title("fill_between() between two curves")canvas.set_legend(True)
print(canvas.render(backend="plotext").build(keep_colors=False)) fill_between() between two curves ┌──────────────────────────────────────────────────────────────────────────┐ 2.8┤┌──────────────────┐ ▗▗▖▄▗▖▄▗▄▗▖▄▗▖▄▗ │ ││ │▘▘ ▘▀▝▖▄▗ between curves │ ││ ▚ between curves │ ▘▀▖▄ │ ││ │ ▝▘▄▗upper │ 2.1┤│ ▚ upper │ lower │ ││ │ ▘▚▗▖ │ ││ ▚ lower │ ▀▖▖ │ ││ │ ▝▝▖▖ │ │└──────────────────┘ ▝▝▘▄ │ 1.3┤ ▝▘▚▗ ▝▘▚▗▖ │ │ ▘▚▗▖ ▀▘│ │ ▀▗▖ │ │ ▝▝▖▄ │ 0.6┤ ▝▘▄ │ │ ▝▘▚▗ │ │ ▘▀▗▖▖ ▄▖│ │ ▝▘▚▗▖▄ ▄▗▖▀▝▘ │-0.2┤ ▝▘▀▝▘▀▝▘▀▘▀▝▘▀▝▘ │ └┬───────────┬───────────┬────────────┬───────────┬───────────┬───────────┬┘ 0.0 0.7 1.3 2.0 2.7 3.3 4.06 · Error bars and reference lines
Section titled “6 · Error bars and reference lines”Reference-line helpers (axhline, axvline, hlines, vlines) are all available, and errorbar() works for scalar or array-shaped errors.
x = np.linspace(1, 10, 9)y = np.sqrt(x)
canvas = Canvas()canvas.errorbar(x, y, yerr=0.15, color="cyan", label="sqrt(x)")canvas.axhline(2.0, color="white")canvas.axvline(4.0, color="yellow")canvas.hlines([1.2, 2.7], xmin=[1, 5], xmax=[3, 9], color="green")canvas.vlines([2.0, 8.0], ymin=[1.0, 2.0], ymax=[1.8, 3.0], color="red")canvas.set_title("Error bars + reference lines")canvas.set_xlabel("x")canvas.set_ylabel("y")canvas.set_legend(True)
print(canvas.render(backend="plotext").build(keep_colors=False)) Error bars + reference lines ┌──────────────────┬────────────────────────────────────────────────────────┐3.3┤┌───────────┐ │ sqrt(x) │ │ ││ │ │ ────────────────────┼────┼──────────────────────────│ ││ ┼ sqrt(x) │ │ ─────────────────┼▗────┼────┼───────────────── │ ││ │ ─┼──────────────────┼─────┼────────────── │2.7┤└───────────┘ │ ▘ │ │ ▝ │ │ ────┼───────┼────┼───────────────── │ │ ───────┼───────┼────┼───────── │ │ │ │ │ │2.1┤ ─────────┼─┼─────────── │ ├──────────────┼───┼─┼─────────────────▝────────────────────────────────────┤ │ ──▘─────┼───┼──── │ │ │ │ │ │1.5┤ ─────┼────── │ │ │ │ │ │ │ ▘ ▘ │ │ │───┼── ▖ │ │0.8┤ │ │ │ └┬───────────┬─────┴──────┬───────────┬───────────┬────────────┬───────────┬┘ 0.5 2.9 5.3 7.8 10.2 12.6 15.0y x7 · Text and annotations
Section titled “7 · Text and annotations”text() is mapped directly. annotate() works too, and when you pass arrowprops, the backend draws a connector line toward the annotated point.
x = np.linspace(0, 6, 120)y = np.sin(x)peak_x = x[np.argmax(y)]peak_y = y.max()
canvas = Canvas()canvas.plot(x, y, color="cyan")canvas.text(0.8, -0.8, "terminal note", color="yellow")canvas.annotate( "peak", xy=(peak_x, peak_y), xytext=(4.4, 0.4), color="white", arrowprops={"color": "green"},)canvas.set_title("Text and annotations")
print(canvas.render(backend="plotext").build(keep_colors=False)) Text and annotations ┌─────────────────────────────────────────────────────────────────────────┐ 1.00┤ ▗▄▖▄▄▖▄▄ │ │ ▗▞▘▘ ▘▀▄▗ │ │ ▄▀ ▀▖ │ │ ▞▝ ▝▝▖ │ 0.50┤ ▗▞ ▝▚ │ │ ▞ ▝▄ peak │ │ ▗▀ ▚ │ │ ▞ ▝▖ │ │ ▞ ▝▘▖ │ 0.00┤▝ ▝▖ │ │ ▝▖▖ │ │ ▝▖ ▗▘│ │ ▝▖▖ ▄▘ │-0.50┤ ▝▖ ▗▘ │ │ ▝▘▖ ▞▘ │ │ terminal note ▝▚▗ ▗▞▘ │ │ ▀▄▗ ▗▄▘▘ │-1.00┤ ▀▀▝▀▀▝▀▘▘ │ └┬───────────┬───────────┬───────────┬───────────┬───────────┬───────────┬┘ 0 1 2 3 4 5 68 · Limits, ticks, grid, and log scales
Section titled “8 · Limits, ticks, grid, and log scales”Axis metadata is one of the nice parts of keeping the same LinePlot API across backends.
x = np.linspace(1, 20, 120)
canvas = Canvas()canvas.plot(x, x**0.5, color="cyan", label="sqrt(x)")canvas.plot(x, np.log(x + 1), color="yellow", label="log(x + 1)")canvas.set_title("Axis controls")canvas.set_xlabel("x")canvas.set_ylabel("value")canvas.set_xlim(1, 20)canvas.set_ylim(0, 5)canvas.set_xticks([1, 2, 5, 10, 20], ["1", "2", "5", "10", "20"])canvas.set_yticks([0, 1, 2, 3, 4, 5])canvas.set_xscale("log")canvas.set_grid(True)canvas.set_legend(True)
print(canvas.render(backend="plotext").build(keep_colors=False)) Axis controls ┌┬┬───────────────────────────────────────────────────────────────────────────┐5┼┌──────────────┐─────────────────────────────────────────────────────────────┤ ││ │ │ ││ ▚ sqrt(x) │ │4┼│ │─────────────────────────────────────────────────────────────┤ ││ ▚ log(x + 1) │ │ ││ │ │3┼└──────────────┘─────────────────────────────────────────────────────────────┤ │▙▛ │ │▟│ │ │▌│ │2┼┼┼───────────────────────────────────────────────────────────────────────────┤ │││ │ │││ │1┼┼┼───────────────────────────────────────────────────────────────────────────┤ │││ │ │││ │0┼┼┼───────────────────────────────────────────────────────────────────────────┤ └┼┴───────────────────────────────────────────────────────────────────────────┘ 5value x9 · Layers
Section titled “9 · Layers”Layer filtering works with the terminal backend too. This is useful when you want progressive reveals or to inspect subsets of a figure.
x = np.linspace(0, 2 * np.pi, 100)
canvas = Canvas()canvas.plot(x, np.sin(x), color="cyan", label="layer 0", layer=0)canvas.plot(x, np.cos(x), color="yellow", label="layer 1", layer=1)canvas.fill_between(x, np.sin(x) + 1.5, 0.0, color="green", label="layer 2", layer=2)canvas.set_title("Layers 0 and 1 only")canvas.set_legend(True)print(canvas.render(backend="plotext", layers=[0]).build(keep_colors=False)) Layers 0 and 1 only ┌─────────────────────────────────────────────────────────────────────────┐ 1.00┤┌───────────┐ ▄▗▄▗▖▄▗▖ │ ││ │▝▘ ▀▗▖ layer 0 │ ││ ▚ layer 0 │ ▝▗▖ │ ││ │ ▚ │ 0.50┤└───────────┘ ▝▖ │ │ ▗▝ ▚ │ │ ▖▘ ▝▖ │ │ ▗▝ ▝▗ │ │ ▘ ▘▖ │ 0.00┤▝ ▝▗ ▗▘│ │ ▘▖ ▖▘ │ │ ▝▖ ▗▝ │ │ ▚ ▖▘ │-0.50┤ ▝▖ ▗▝ │ │ ▚ ▗▘▘ │ │ ▝▘▖ ▗▖▘ │ │ ▝▘▄ ▗▖▀ │-1.00┤ ▝▘▀▝▘▀▘▀ │ └┬───────────┬───────────┬───────────┬───────────┬───────────┬───────────┬┘ 0.0 1.0 2.1 3.1 4.2 5.2 6.3print(canvas.render(backend="plotext", layers=[1]).build(keep_colors=False)) Layers 0 and 1 only ┌─────────────────────────────────────────────────────────────────────────┐ 1.00┤┌───────────┐ ▗▗▖▄▖│ ││ │ layer 1 ▗▗▘▘ │ ││ ▚ layer 1 │ ▗▘▘ │ ││ │ ▖▘ │ 0.50┤└───────────┘ ▄▝ │ │ ▝▖ ▗▘ │ │ ▚ ▞ │ │ ▗ ▖ │ │ ▚ ▞ │ 0.00┤ ▝▖ ▗▘ │ │ ▚ ▞ │ │ ▝▖ ▗▘ │ │ ▚ ▞ │-0.50┤ ▝▖ ▗▘ │ │ ▝▗▖ ▗▖▘ │ │ ▚ ▞ │ │ ▝▘▄▗ ▖▄▝▘ │-1.00┤ ▀▝▘▀▝▘▀ │ └┬───────────┬───────────┬───────────┬───────────┬───────────┬───────────┬┘ 0.0 1.0 2.1 3.1 4.2 5.2 6.3print(canvas.render(backend="plotext", layers=[0, 1]).build(keep_colors=False)) Layers 0 and 1 only ┌─────────────────────────────────────────────────────────────────────────┐ 1.00┤┌───────────┐ ▄▗▄▗▖▄▗▖ ▗▗▖▄▖│ ││ │▝▘ ▀▗▖ layer 0 ▗▗▘▘ │ ││ ▚ layer 0 │ ▝▗▖ ▗▘▘ │ ││ │ ▚ layer 1 ▖▘ │ 0.50┤│ ▚ layer 1 │ ▝▖ ▄▝ │ ││ │▝▖ ▚ ▗▘ │ │└───────────┘ ▚ ▝▖ ▞ │ │ ▗▝ ▗ ▝▗ ▖ │ │ ▘ ▚ ▘▖ ▞ │ 0.00┤▝ ▝▖ ▝▗ ▗▘ ▗▘│ │ ▚ ▘▖ ▞ ▖▘ │ │ ▝▖ ▝▖ ▗▘ ▗▝ │ │ ▚ ▚ ▞ ▖▘ │-0.50┤ ▝▖ ▝▖ ▗▘ ▗▝ │ │ ▝▗▖ ▚▗▖▘ ▗▘▘ │ │ ▚ ▞▝▘▖ ▗▖▘ │ │ ▝▘▄▗ ▖▄▝▘ ▝▘▄ ▗▖▀ │-1.00┤ ▀▝▘▀▝▘▀ ▝▘▀▝▘▀▘▀ │ └┬───────────┬───────────┬───────────┬───────────┬───────────┬───────────┬┘ 0.0 1.0 2.1 3.1 4.2 5.2 6.310 · Multi-subplot canvases
Section titled “10 · Multi-subplot canvases”Subplots are fully supported, including figure-level titles via canvas.suptitle(...).
x = np.linspace(0, 2 * np.pi, 80)rng = np.random.default_rng(5)
canvas, (ax1, ax2) = Canvas.subplots(ncols=2)
ax1.plot(x, np.sin(x), color="cyan", label="sin(x)")ax1.plot(x, np.cos(x), color="yellow", label="cos(x)")ax1.set_title("Signals")ax1.set_xlabel("x")ax1.set_ylabel("value")ax1.set_legend(True)
cats = np.arange(6)vals = rng.integers(2, 9, size=6)ax2.bar(cats, vals, color="green", label="count")ax2.scatter(cats, vals, color="red", label="points")ax2.set_title("Counts")ax2.set_xlabel("bin")ax2.set_ylabel("value")ax2.set_legend(True)
canvas.suptitle("Terminal dashboard")print(canvas.render(backend="plotext").build(keep_colors=False))Terminal dashboard Signals Counts ┌─────────────────────────────────┐ ┌───────────────────────────────────┐ 1.00┤┌──────────┐ ▗▄▖│7.0┤┌──────────┐ ██▖██ │ ││ │ sin(x) ▗▘ │ ││ │ █████count │ ││ ▚ sin(x) │▖ ▞▘ │ ││ █ count │ █████ │ ││ │▝▘ cos(x)▘ │ ││ │ █████points │ 0.50┤│ ▚ cos(x) │ ▚ ▗▘ │5.2┤│ ▚ points │ █████ │ ││ │ ▚ ▖ │ ││ │ ████████▘█████▘██│ │└──────────┘ ▌ ▗▘ │ │└──────────┘ █████████████████│ │▗▘ ▝ ▗ ▘ │ │████████████ █████████████████│ 0.00┤▗ ▚ ▚ ▞ ▖│3.5┤████████████ █████████████████│ │ ▌ ▘ ▐ ▗▘│ │████████████ █████████████████│ │ ▗ ▐ ▖ ▘ │ │████████████ █████████████████│ │ ▚ ▚ ▞ ▞ │ │██████████████▗██ █████████████████│-0.50┤ ▌ ▚ ▐ ▗▘ │1.8┤█████████████████ █████████████████│ │ ▝▖ ▗▄▘ ▗▘ │ │█████████████████ █████████████████│ │ ▝▖ ▗▀▖ ▌ │ │█████████████████ █████████████████│ │ ▀▄ ▄▀ ▐▖ ▄▀ │ │█████████████████ █████████████████│-1.00┤ ▀▀▀ ▝▀▀▀▘ │0.0┤█████████████████ █████████████████│ └┬────┬─────┬────┬────┬─────┬────┬┘ └──┬─────┬─────┬─────┬─────┬─────┬──┘ 0.0 1.0 2.1 3.1 4.2 5.2 6.3 0 1 2 3 4 5value x value bin11 · Matrix-style imshow() output
Section titled “11 · Matrix-style imshow() output”add_imshow() is rendered as a terminal matrix plot. This is the terminal approximation of image-like numeric data, not a full matplotlib colormap implementation.
data = np.arange(1, 26).reshape(5, 5)
canvas = Canvas()canvas.imshow(data)canvas.set_title("Matrix-style imshow")canvas.set_xlabel("column")canvas.set_ylabel("row")
print(canvas.render(backend="plotext").build(keep_colors=False)) Matrix-style imshow ┌─────────────────────────────────────────────────────────────────────────────┐4┤█ █ █ █ █│ │ │ │ │ │ │3┤█ █ █ █ █│ │ │ │ │ │ │2┤█ █ █ █ █│ │ │ │ │ │ │1┤█ █ █ █ █│ │ │ │ │ │ │0┤█ █ █ █ █│ └┬────────────┬───────────┬────────────┬────────────┬───────────┬────────────┬┘ 0.0 0.7 1.3 2.0 2.7 3.3 4.0row column12 · Patches and arbitrary patch geometry
Section titled “12 · Patches and arbitrary patch geometry”The backend supports common matplotlib patch types directly and also uses matplotlib patch geometry as a best-effort fallback for many other patch subclasses.
matplotlib.patches.Rectanglematplotlib.patches.Circlematplotlib.patches.Polygonmatplotlib.patches.Ellipse- many other patch types that expose a usable matplotlib path
That is enough for many annotations, regions of interest, and geometric callouts.
canvas = Canvas()canvas.add_patch( mpatches.Rectangle( (0.2, 0.2), 1.3, 0.7, fill=False, edgecolor="yellow", label="window" ))canvas.add_patch( mpatches.Circle((2.2, 1.6), 0.45, fill=False, edgecolor="cyan", label="sensor"))canvas.add_patch( mpatches.Polygon( [[3.0, 0.5], [3.8, 1.2], [3.4, 2.0]], fill=True, facecolor="green", label="region", ))canvas.add_patch( mpatches.Ellipse( (2.8, 1.0), 0.8, 0.5, fill=False, edgecolor="white", label="ellipse" ))canvas.set_xlim(0, 4.5)canvas.set_ylim(0, 2.5)canvas.set_title("Supported patch types")canvas.set_legend(True)
print(canvas.render(backend="plotext").build(keep_colors=False)) Supported patch types ┌───────────────────────────────────────────────────────────────────────────┐2.5┤┌───────────┐ │ ││ │ window │ ││ ▚ window │ │ ││ │ ▗ ▗ ▗ ▗ ▗ sensor │1.9┤│ ▚ sensor │ ▘ ▝ region │ ││ │ ▖▘ ▘▖ ellipse │ ││ ▚ region │ ▖ ▖ │ ││ │ ▘ ▘ │ ││ ▚ ellipse │ ▘▖ ▗ ▘ │1.2┤│ │ ▝ ▗ ▗ ▗ ▐▝ ▘▝ ▝ ▘▝ ▘▖ ▝ │ │└───────────┘ ▐ ▌ │ │ ▝ ▘ ▝▗▗ ▖▖▘ │ │ ▘▝ ▝ ▘▝ │0.6┤ │ │ ▝ │ │ │ │ ▝ ▘ │0.0┤ │ └┬───────────┬────────────┬───────────┬───────────┬────────────┬───────────┬┘ 0.0 0.8 1.5 2.2 3.0 3.8 4.5Plotly backend note (patches)
Section titled “Plotly backend note (patches)”Plotly can render common Matplotlib patches too. Note: Plotly “shapes” do not appear in legends, so maxplotlib adds a small dummy legend entry for labeled patches.
# Interactive rendering of the same canvascanvas.show(backend="plotly")13 · Captions, symlog scales, aspect, and colorbar notes
Section titled “13 · Captions, symlog scales, aspect, and colorbar notes”The last backend-specific pieces of the LinePlot surface also work in the terminal backend:
add_caption(...)set_xscale('symlog')andset_yscale('symlog')set_aspect(...)with a best-effort terminal plotsize approximationadd_colorbar(...)as a note-style value summary for matrix/image output
x = np.linspace(-20, 20, 161)
canvas = Canvas()canvas.plot(x, x**3, color="cyan", label="x^3")canvas.set_title("Symlog example")canvas.add_caption("caption text")canvas.set_xscale("symlog")canvas.set_yscale("symlog")canvas.set_aspect("equal")canvas.set_legend(True)
print(canvas.render(backend="plotext").build(keep_colors=False)) Symlog example ┌──────────────────────────────────┐ 3.9┤┌───────┐ ▄▖│ ││ │ ▄▟▀▘ │ ││ ▚ x^3 │ ▗▄▀▘ │ 2.0┤│ │ ▗▞▀ │ │└───────┘ ▄▀▘ │ │ ▖▖▄▀ │ 0.0┤ ▄▀▝▝ ▘ │ │ ▗▄▀ │-2.0┤ ▄▞▘ │ │ ▗▄▀▘ │ │ ▗▄▛▀ │-3.9┤▝▀ │ └┬─────┬──────────┬────┬────┬──────┘ -1.32 -0.88 0.00 0.44 0.88heat = np.arange(1, 26).reshape(5, 5)
canvas = Canvas()canvas.imshow(heat)canvas.add_colorbar(label="intensity")canvas.set_title("Matrix + colorbar note")
print(canvas.render(backend="plotext").build(keep_colors=False)) Matrix + colorbar note | intensity: 1..25 ┌─────────────────────────────────────────────────────────────────────────────┐4┤█ █ █ █ █│ │ │ │ │ │ │3┤█ █ █ █ █│ │ │ │ │ │ │ │ │2┤█ █ █ █ █│ │ │ │ │ │ │1┤█ █ █ █ █│ │ │ │ │ │ │0┤█ █ █ █ █│ └┬────────────┬───────────┬────────────┬────────────┬───────────┬────────────┬┘ 0.0 0.7 1.3 2.0 2.7 3.3 4.014 · Saving terminal output to a file
Section titled “14 · Saving terminal output to a file”Saving with the plotext backend writes the rendered terminal figure to a text file. By default the saved text is plain and easy to inspect in editors, CI logs, or generated artifacts.
Plotly backend note (symlog)
Section titled “Plotly backend note (symlog)”Plotly has no native symlog axis type. For symlog, maxplotlib applies a symmetric log transform to the data and uses a linear Plotly axis.
This keeps the plot working across backends, but tick formatting may differ from Matplotlib/plotext.
canvas.show(backend="plotly")x = np.linspace(0, 2 * np.pi, 60)
canvas = Canvas()canvas.plot(x, np.sin(x), color="cyan", label="sin(x)")canvas.set_title("Saved terminal figure")terminal_fig = canvas.render(backend="plotext")
output_path = Path("plotext_output.txt")terminal_fig.savefig(output_path)print(output_path.read_text().splitlines()[0]) Saved terminal figure15 · A simple terminal animation
Section titled “15 · A simple terminal animation”plotext does not provide browser-widget animation like Plotly or Matplotlib’s GUI animation stack, but terminal animation is still practical: render a frame, clear the output, sleep briefly, and repeat.
The example below works well in a notebook cell or a terminal Python session. In notebooks, clear_output(wait=True) keeps the cell output updating in place.
import time
from IPython.display import clear_output
x = np.linspace(0, 2 * np.pi, 120)
for phase in np.linspace(0, 2 * np.pi, 24): canvas = Canvas() canvas.plot(x, np.sin(x + phase), color="cyan", label="sin(x + phase)") canvas.plot(x, np.cos(x + phase), color="yellow", label="cos(x + phase)") canvas.set_ylim(-1.2, 1.2) canvas.set_title(f"Animated phase = {phase:.2f}") canvas.set_legend(True)
clear_output(wait=True) print(canvas.render(backend="plotext").build(keep_colors=False)) time.sleep(0.08) Animated phase = 6.28 ┌──────────────────────────────────────────────────────────────────────────┐ 1.2┤┌──────────────────┐ │ ││ │▖ sin(x + phase) ▄▖│ ││ ▚ sin(x + phase) │▝▀▝▚▖▖ ▄▗▀▘▀ │ ││ │ ▝▚▖ cos(x + phase)▗▀ │ 0.6┤│ ▚ cos(x + phase) │ ▀▄ ▖▞▘ │ ││ │ ▝▄ ▗▞ │ │└──────────────────┘ ▘▄ ▗▖▘ │ │ ▄▝ ▝▖▖ ▚ ▗▗▘ │ │ ▞ ▝▖ ▘▚ ▗▘ │ 0.0┤▝ ▝▝▖ ▚▗ ▗▘▘ ▞▘│ │ ▝▄ ▚ ▄▘ ▖▀ │ │ ▝▄ ▀▗ ▄▘ ▗▞ │ │ ▘▄ ▀▖ ▄▝ ▗▞ │-0.6┤ ▚▖ ▀▄▗▞ ▗▝▘ │ │ ▀▄ ▄▀▝▚▖ ▗▖▞▘ │ │ ▝▀▖▄▄ ▄▄▗▀▘ ▝▝▚▖▄▖ ▗▄▖▀▘ │ │ ▘▀▀▝ ▝▘▀▀ │-1.2┤ │ └┬───────────┬───────────┬────────────┬───────────┬───────────┬───────────┬┘ 0.0 1.0 2.1 3.1 4.2 5.2 6.316 · Current limitations
Section titled “16 · Current limitations”| Feature | Status | Notes |
|---|---|---|
add_colorbar() | ⚠️ | rendered as a compact note-style min/max summary rather than a true continuous side bar |
| Arbitrary matplotlib patch subclasses | ⚠️ | best-effort path extraction works for many patch types, but not every custom patch will map perfectly |
| Exact matplotlib styling parity | ❌ | Terminal rendering is approximate by design |
| Notebook-style high-frame-rate animation | ⚠️ | possible via redraw loops, but not a substitute for GUI animation widgets |
For terminal work, this backend now covers a large and practical portion of the LinePlot API. When you need richer styling, publication export, or interactive browser behavior, use the matplotlib, tikzfigure, or plotly backends instead.