Tutorial 09 - 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

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.

[1]:
from pathlib import Path

import matplotlib.patches as mpatches
import numpy as np

from maxplotlib import Canvas

1 · Figure lifecycle: plot(), build(), show(), and savefig()

Use canvas.plot(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.

[2]:
x = np.linspace(0, 2 * np.pi, 100)

canvas, ax = Canvas.subplots()
ax.plot(x, np.sin(x), color="cyan", label="sin(x)")
ax.set_title("Demo")
ax.set_xlabel("x")
ax.set_ylabel("y")

terminal_fig = canvas.plot(backend="plotext")
preview = terminal_fig.build(keep_colors=False)
print(preview)

# terminal_fig.show()
# canvas.show(backend="plotext")
                                        Demo
     ┌─────────────────────────────────────────────────────────────────────────┐
 1.00┤ ▞▞ sin(x)   ▄▄▞▀▀▀▀▀▄▄                                                  │
     │           ▞▀          ▀▚▄                                               │
     │         ▞▀               ▀▄▖                                            │
 0.67┤       ▗▀                   ▝▄                                           │
     │     ▄▀▘                      ▚▖                                         │
 0.33┤   ▗▞                          ▝▖                                        │
     │  ▄▘                            ▝▚                                       │
     │▗▞                                ▀▖                                     │
 0.00┤▘                                  ▝▚                                   ▞│
     │                                     ▀▖                                ▄▘│
     │                                      ▝▚                             ▗▞  │
-0.33┤                                        ▀▖                          ▄▘   │
     │                                         ▝▄                       ▄▞     │
-0.67┤                                           ▚▖                   ▗▀       │
     │                                            ▝▀▖               ▄▞▘        │
     │                                              ▝▀▄▖          ▄▞           │
-1.00┤                                                 ▝▀▀▄▄▄▄▄▄▀▀             │
     └┬─────────────────┬─────────────────┬─────────────────┬─────────────────┬┘
     0.0               1.6               3.1               4.7              6.3
y                                         x

2 · Line plots

Line plots are the natural fit for plotext. Labels, markers, grid lines, axis titles, and legends all carry over cleanly.

[3]:
x = np.linspace(0, 2 * np.pi, 120)

canvas, ax = Canvas.subplots()
ax.plot(x, np.sin(x), color="cyan", label="sin(x)")
ax.plot(x, np.cos(x), color="yellow", label="cos(x)", marker="dot")
ax.plot(x, np.sin(2 * x), color="green", label="sin(2x)")
ax.set_title("Multiple terminal lines")
ax.set_xlabel("x")
ax.set_ylabel("value")
ax.set_grid(True)
ax.set_legend(True)

print(canvas.plot(backend="plotext").build(keep_colors=False))
                               Multiple terminal lines
     ┌┬─────────────────┬─────────────────┬─────────────────┬─────────────────┬┐
 1.00┼ ▞▞ sin(x)  ─▗▄▄▀▀▀▀▀▄▄─────────────┼──────▗▞▀▀▄──────┼─────────────•••••┤
     │ •• cos(x)  ▚▖    │    ▀▀▄          │     ▞▘    ▚ sin(x)         ••••   ││
     │ ▞▞ sin(2x)  ▐    │       ▀▚        │    ▞       ▀▖   │        •••      ││
 0.67┼┼──▗▘─▗▄▘─••──▚───┼─────────▀▖──────┼──▗▀─────────cos(x)─────••─────────┼┤
     ││ ▗▘ ▗▘    •••▝▖  │          ▝▚▖    │  ▌          sin(2x)  •••          ││
 0.33┼┼─▞▗▄▘───────••▝▌─┼────────────▝▄───┼─▐─────────────▚─┼───••────────────┼┤
     ││▞▗▘           •▝▖│              ▚▖ │▗▘             ▝▖│ ••              ││
     │▐▄▘             •▚│               ▝▄▗▘               ▐│••               ││
 0.00┼▌─────────────────▚─────────────────▞─────────────────▚─────────────────▗┤
     ││                 ▝▖•              ▐│▚▖             ••│▌              ▗▗▘│
     ││                 │▐ •            ▗▘│ ▝▚           •  │▝▖            ▗▘▞││
-0.33┼┼─────────────────┼─▌─••─────────▗▘─┼───▚▖───────••───┼─▚──────────▗▀▘▞─┼┤
     ││                 │ ▝▄  ••       ▞  │    ▝▚    ••     │  ▜        ▗▘ ▐  ││
-0.67┼┼─────────────────┼──▝▖──•••────▗▘──┼──────▀▄•••──────┼───▚─────▗▀▘─▗▘──┼┤
     ││                 │   ▝▖   ••  ▞▘   │       ••▄       │   ▝▖  ▄▞▘  ▗▘   ││
     ││                 │    ▝▚    •▞•    │    •••   ▀▄▄    │    ▝▄▖    ▗▘    ││
-1.00┼┼─────────────────┼──────▀▄▄▞▀──•••••••••─────────▀▚▄▄▄▄▄▞▀▘─▝▄▄▄▀▘─────┼┤
     └┼─────────────────┼─────────────────┼─────────────────┼─────────────────┼┘
     0.0               1.6               3.1               4.7              6.3
value                                     x

3 · Scatter plots

Scatter traces use plotext.scatter(...) under the hood. They combine well with a line on the same axes.

[4]:
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, ax = Canvas.subplots()
ax.plot(x, np.sin(x), color="white", label="sin(x)")
ax.scatter(samples_x, samples_y, color="red", marker="x", label="samples")
ax.set_title("Scatter + line")
ax.set_xlabel("x")
ax.set_ylabel("y")
ax.set_legend(True)

print(canvas.plot(backend="plotext").build(keep_colors=False))
                                   Scatter + line
     ┌─────────────────────────────────────────────────────────────────────────┐
 1.13┤ ▞▞ sin(x)     x                                                   x     │
     │ xx samples ▞▀▀▀▚▖                                sin(x)           ▗▄▄▀▀x│
     │        ▗▄▀      ▝▀▄                                             ▄▞▘     │
 0.77┤       ▞▘           ▀▚▖                           samples       ▞        │
     │     ▄▀               ▝▄                                      x▀         │
 0.41┤   ▗▞                x  ▚                                    ▞           │
     │  ▗▘ x                   ▚▖                                ▗▀            │
     │ ▄▘                       ▝▖                             x▗▘             │
 0.05┤x                         x▝▄                            ▗▘              │
     │                             ▚                          ▞▘               │
     │                              ▚x                       ▞                 │
-0.31┤                               ▚▖                  x ▗▀                  │
     │                                ▝▖                  ▗▘                   │
-0.68┤                                 ▝▀▖              ▗▞▘                    │
     │                                   ▝x▖           ▄▘                      │
     │                                     ▝▄▄      ▗▄▀                        │
-1.04┤                                        ▀x▀▀▀▀x                          │
     └┬─────────────────┬─────────────────┬─────────────────┬─────────────────┬┘
      0                 2                 4                 6                 8
y                                         x

4 · Bar charts

Bars are supported too, so summary views and simple dashboards work well in the terminal.

[5]:
bins = np.arange(5)
values = np.array([4.0, 6.5, 3.2, 7.4, 5.8])

canvas, ax = Canvas.subplots()
ax.bar(bins, values, color="green", label="count")
ax.scatter(bins, values, color="yellow", label="sample mean")
ax.set_title("Bar + scatter overlay")
ax.set_xlabel("bin")
ax.set_ylabel("value")
ax.set_legend(True)

print(canvas.plot(backend="plotext").build(keep_colors=False))
                               Bar + scatter overlay
   ┌───────────────────────────────────────────────────────────────────────────┐
7.4┤ ██ count                                     ███████▘██████               │
   │ ▞▞ sample mean                               ███count██████               │
   │               ██████▝███████                 ██████████████               │
6.2┤               ██████████████                 ███sample mean  ██████▗██████│
   │               ██████████████                 ██████████████  █████████████│
4.9┤               ██████████████                 ██████████████  █████████████│
   │               ██████████████                 ██████████████  █████████████│
   │██████▖██████  ██████████████                 ██████████████  █████████████│
3.7┤█████████████  ██████████████                 ██████████████  █████████████│
   │█████████████  ██████████████  ██████▗██████  ██████████████  █████████████│
   │█████████████  ██████████████  █████████████  ██████████████  █████████████│
2.5┤█████████████  ██████████████  █████████████  ██████████████  █████████████│
   │█████████████  ██████████████  █████████████  ██████████████  █████████████│
1.2┤█████████████  ██████████████  █████████████  ██████████████  █████████████│
   │█████████████  ██████████████  █████████████  ██████████████  █████████████│
   │█████████████  ██████████████  █████████████  ██████████████  █████████████│
0.0┤█████████████  ██████████████  █████████████  ██████████████  █████████████│
   └──────┬───────────────┬──────────────┬──────────────┬───────────────┬──────┘
         0.0             1.0            2.0            3.0             4.0
value                                   bin

5 · Filled regions

The backend supports both common fill_between(...) shapes:

  1. Filling down to a scalar baseline

  2. Filling the region between two full curves

[6]:
x = np.linspace(0, 5, 100)

canvas, ax = Canvas.subplots()
ax.fill_between(
    x,
    np.exp(-0.5 * x) * np.sin(3 * x) + 1.0,
    0.0,
    color="cyan",
    label="signal envelope",
)
ax.set_title("fill_between() to a scalar baseline")
ax.set_xlabel("x")
ax.set_ylabel("amplitude")
ax.set_legend(True)

print(canvas.plot(backend="plotext").build(keep_colors=False))
                         fill_between() to a scalar baseline
    ┌──────────────────────────────────────────────────────────────────────────┐
1.78┤ ▞▞ signal envelope                                                       │
    │█████████▙                                                                │
    │██████████▙                                        signal envelope        │
1.57┤███████████▖                                                              │
    │████████████▖                                                             │
1.37┤████████████▙                                                             │
    │█████████████▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▖                                   │
    │████████████████████████████████████████▙▄                                │
1.16┤███████████████████████████████████████████▖                              │
    │███████████████████████████████████████████████████████████████████████▄▄▄│
    │██████████████████████████████████████████████████████████████▛▀          │
0.95┤███████████████████████████████████████████████████████████▛▀             │
    │███████████████████████████████████████████████████████▛▀▀                │
0.74┤████████████████████████████▘                                             │
    │███████████████████████████▘                                              │
    │██████████████████████████▘                                               │
0.54┤████████████████████████▀                                                 │
    └┬─────────────────┬──────────────────┬─────────────────┬─────────────────┬┘
    0.0               1.2                2.5               3.8              5.0
amplitude                                 x

[7]:
x = np.linspace(0, 4, 100)
upper = np.sin(x) + 1.8
lower = np.cos(x) + 0.8

canvas, ax = Canvas.subplots()
ax.fill_between(x, upper, lower, color="blue", label="between curves")
ax.plot(x, upper, color="white", label="upper")
ax.plot(x, lower, color="yellow", label="lower")
ax.set_title("fill_between() between two curves")
ax.set_legend(True)

print(canvas.plot(backend="plotext").build(keep_colors=False))
                          fill_between() between two curves
     ┌─────────────────────────────────────────────────────────────────────────┐
 2.80┤ ▞▞ between curves  ▄▄▄▞▀▀▀▀▀▀▀▀▀▀▄▄▄▄                                   │
     │ ▞▞ upper          ▀██████████████████▀▀▀▄▄       between curves         │
     │ ▞▞ lower          ████████████████████████▀▀▄▖                          │
 2.30┤       ▗▄▀▘███████████████████████████████████▝▀▀▄upper                  │
     │   ▗▄▀▀▘██████████████████████████████████████████lower                  │
     │▗▄▀▘██████████████████████████████████████████████████▀▚▄                │
 1.80┤▀▀▀▀▀▀▀▄▄▄▄██████████████████████████████████████████████▀▚▄             │
     │           ▀▀▄▄█████████████████████████████████████████████▀▚▄▖         │
 1.30┤               ▀▀▚▄████████████████████████████████████████████▝▀▄▖      │
     │                   ▀▀▄▖███████████████████████████████████████████▝▀▚▄   │
     │                      ▝▀▄▖████████████████████████████████████████████▀▀▀│
 0.80┤                         ▝▀▚▄████████████████████████████████████████████│
     │                             ▀▚▄█████████████████████████████████████████│
     │                                ▀▚▄██████████████████████████████████████│
 0.30┤                                   ▀▀▄▖██████████████████████████████████│
     │                                      ▝▀▀▄▄█████████████████████████████▄│
     │                                           ▀▀▄▄▄██████████████████▗▄▄▞▀▀ │
-0.20┤                                                ▀▀▀▀▄▄▄▄▄▄▄▄▄▄▄▀▀▀▘      │
     └┬─────────────────┬─────────────────┬─────────────────┬─────────────────┬┘
      0                 1                 2                 3                 4

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.

[8]:
x = np.linspace(1, 10, 9)
y = np.sqrt(x)

canvas, ax = Canvas.subplots()
ax.errorbar(x, y, yerr=0.15, color="cyan", label="sqrt(x)")
ax.axhline(2.0, color="white")
ax.axvline(4.0, color="yellow")
ax.hlines([1.2, 2.7], xmin=[1, 5], xmax=[3, 9], color="green")
ax.vlines([2.0, 8.0], ymin=[1.0, 2.0], ymax=[1.8, 3.0], color="red")
ax.set_title("Error bars + reference lines")
ax.set_xlabel("x")
ax.set_ylabel("y")
ax.set_legend(True)

print(canvas.plot(backend="plotext").build(keep_colors=False))
                            Error bars + reference lines
    ┌────────────────────────┬─────────────────────────────────────────────────┐
3.24┤                        │                                                ││
    │                        │                          sqrt(x)      │        ┼│
    │                        │                                ▌      ┼         │
2.85┤                        │                              ┼ ▌                │
    │                        │       ▝▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▌▀▀▀▀▀▀▀▀        │
2.47┤                        │                     ┼          ▌                │
    │                        │            ┼                   ▌                │
    │                        │  │         │                   ▌                │
2.08┤                        │  ┼                             ▌                │
    ├──────────────────│─────┼────────────────────────────────▘────────────────┤
    │        ▖         ┼     │                                                 │
1.70┤        ▌               │                                                 │
    │        ▌┼              │                                                 │
1.31┤        ▌│              │                                                 │
    │▄▄▄▄▄▄▄▄▌▄▄▄▄▄▄▄▄       │                                                 │
    │┼       ▌               │                                                 │
0.93┤│       ▘               │                                                 │
    └┬─────────────────┬─────┴────────────┬─────────────────┬─────────────────┬┘
    1.0               3.2                5.5               7.8             10.0
y                                         x

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.

[9]:
x = np.linspace(0, 6, 120)
y = np.sin(x)
peak_x = x[np.argmax(y)]
peak_y = y.max()

canvas, ax = Canvas.subplots()
ax.plot(x, y, color="cyan")
ax.text(0.8, -0.8, "terminal note", color="yellow")
ax.annotate(
    "peak",
    xy=(peak_x, peak_y),
    xytext=(4.4, 0.4),
    color="white",
    arrowprops={"color": "green"},
)
ax.set_title("Text and annotations")

print(canvas.plot(backend="plotext").build(keep_colors=False))
                                Text and annotations
     ┌─────────────────────────────────────────────────────────────────────────┐
 1.00┤              ▄▄▞▀▀▚▄▄▖▄▖                                                │
     │           ▗▄▀        ▝▀▀▚▄▄▖                                            │
     │         ▄▞▘              ▝▀▝▀▀▚▄▄▖                                      │
 0.67┤        ▞                    ▚▄   ▝▀▀▀▄▄▄                                │
     │      ▞▀                       ▚         ▀▀▀▄▄▄                          │
     │    ▗▞                          ▀▚             ▀▀▀▄peak                  │
 0.33┤   ▞▘                             ▚                                      │
     │  ▞                                ▀▄                                    │
 0.00┤▗▀                                   ▚▖                                  │
     │▘                                     ▝▖                                 │
     │                                       ▝▖                                │
-0.33┤                                        ▝▀▖                             ▞│
     │                                          ▝▄                          ▗▞ │
     │                                            ▀▖                       ▞▘  │
-0.67┤                                             ▝▄▖                   ▄▀    │
     │    terminal note                              ▝▄                ▄▞      │
     │                                                 ▀▚▄▖         ▄▄▀        │
-1.00┤                                                    ▝▀▄▄▄▄▄▄▞▀           │
     └┬─────────────────┬─────────────────┬─────────────────┬─────────────────┬┘
     0.0               1.5               3.0               4.5              6.0

8 · Limits, ticks, grid, and log scales

Axis metadata is one of the nice parts of keeping the same LinePlot API across backends.

[10]:
x = np.linspace(1, 20, 120)

canvas, ax = Canvas.subplots()
ax.plot(x, x**0.5, color="cyan", label="sqrt(x)")
ax.plot(x, np.log(x + 1), color="yellow", label="log(x + 1)")
ax.set_title("Axis controls")
ax.set_xlabel("x")
ax.set_ylabel("value")
ax.set_xlim(1, 20)
ax.set_ylim(0, 5)
ax.set_xticks([1, 2, 5, 10, 20], ["1", "2", "5", "10", "20"])
ax.set_yticks([0, 1, 2, 3, 4, 5])
ax.set_xscale("log")
ax.set_grid(True)
ax.set_legend(True)

print(canvas.plot(backend="plotext").build(keep_colors=False))
                                  Axis controls
 ┌─────────────────────────────────────────────────────────────────────────────┐
5┼ ▞▞ sqrt(x)    ──────────────────────────────────────────────────────────────┤
 │ ▞▞ log(x + 1)                                                               │
 │ ▌                                                                           │
4┼▗▌───────────────────────────────────────────────────────────────────────────┤
 │▐                                                                            │
 │▟                                                                            │
3┼▌▖───────────────────────────────────────────────────────────────────────────┤
 │▗▌                                                                           │
 │▛                                                                            │
 │▘                                                                            │
2┼─────────────────────────────────────────────────────────────────────────────┤
 │                                                                             │
 │                                                                             │
1┼─────────────────────────────────────────────────────────────────────────────┤
 │                                                                             │
 │                                                                             │
0┼─────────────────────────────────────────────────────────────────────────────┤
 └─────────────────────────────────────────────────────────────────────────────┘
 5
value                                   x

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.

[11]:
x = np.linspace(0, 2 * np.pi, 100)

canvas, ax = Canvas.subplots()
ax.plot(x, np.sin(x), color="cyan", label="layer 0", layer=0)
ax.plot(x, np.cos(x), color="yellow", label="layer 1", layer=1)
ax.fill_between(x, np.sin(x) + 1.5, 0.0, color="green", label="layer 2", layer=2)
ax.set_title("Layers 0 and 1 only")
ax.set_legend(True)
[12]:
print(canvas.plot(backend="plotext", layers=[0]).build(keep_colors=False))
                                 Layers 0 and 1 only
     ┌─────────────────────────────────────────────────────────────────────────┐
 1.00┤ ▞▞ layer 0   ▄▞▀▀▀▀▀▄▄                                                  │
     │           ▄▞▀         ▀▚▖                        layer 0                │
     │         ▄▞              ▝▀▖                                             │
 0.67┤       ▗▀                  ▝▚                                            │
     │      ▞▘                     ▀▄▖                                         │
     │    ▗▀                         ▐                                         │
 0.33┤   ▞▘                           ▚▖                                       │
     │ ▗▀                              ▝▄                                      │
 0.00┤▞▘                                 ▚▖                                   ▗│
     │                                    ▝▄                                  ▌│
     │                                      ▚▖                              ▗▀ │
-0.33┤                                       ▝▄                            ▞▘  │
     │                                        ▝▖                         ▗▀    │
     │                                         ▝▀▖                      ▞▘     │
-0.67┤                                           ▝▚                   ▗▀       │
     │                                             ▀▄▖              ▄▀▘        │
     │                                               ▝▄▖         ▗▄▀           │
-1.00┤                                                 ▝▀▀▄▄▄▄▄▄▀▘             │
     └┬─────────────────┬─────────────────┬─────────────────┬─────────────────┬┘
     0.0               1.6               3.1               4.7              6.3

[13]:
print(canvas.plot(backend="plotext", layers=[1]).build(keep_colors=False))
                                 Layers 0 and 1 only
     ┌─────────────────────────────────────────────────────────────────────────┐
 1.00┤ ▞▞ layer 1                                                         ▄▄▞▀▀│
     │     ▀▚▄                                          layer 1        ▄▞▀     │
     │        ▚▖                                                     ▗▞        │
 0.67┤         ▝▀▖                                                 ▗▀▘         │
     │           ▝▄                                               ▄▘           │
     │             ▚▖                                           ▗▞             │
 0.33┤              ▝▄                                         ▄▘              │
     │                ▚                                       ▞                │
 0.00┤                 ▚                                     ▞                 │
     │                  ▀▖                                 ▗▀                  │
     │                   ▝▚                               ▞▘                   │
-0.33┤                     ▚▖                           ▗▞                     │
     │                      ▝▄                         ▄▘                      │
     │                        ▚▖                     ▗▞                        │
-0.67┤                         ▝▄                   ▄▘                         │
     │                           ▚▄               ▄▞                           │
     │                             ▀▄▖         ▗▄▀                             │
-1.00┤                               ▝▀▚▄▄▄▄▄▞▀▘                               │
     └┬─────────────────┬─────────────────┬─────────────────┬─────────────────┬┘
     0.0               1.6               3.1               4.7              6.3

[14]:
print(canvas.plot(backend="plotext", layers=[0, 1]).build(keep_colors=False))
                                 Layers 0 and 1 only
     ┌─────────────────────────────────────────────────────────────────────────┐
 1.00┤ ▞▞ layer 0   ▄▞▀▀▀▀▀▄▄                                             ▄▄▞▀▀│
     │ ▞▞ layer 1 ▞▀         ▀▚▖                        layer 0        ▄▞▀     │
     │        ▚▖▞              ▝▀▖                                   ▗▞        │
 0.67┤       ▗▀▝▀▖               ▝▚                     layer 1    ▗▀▘         │
     │      ▞▘   ▝▄                ▀▄▖                            ▄▘           │
     │    ▗▀       ▚▖                ▐                          ▗▞             │
 0.33┤   ▞▘         ▝▄                ▚▖                       ▄▘              │
     │ ▗▀             ▚                ▝▄                     ▞                │
 0.00┤▞▘               ▚                 ▚▖                  ▞                 │
     │▘                 ▀▖                ▝▄               ▗▀                 ▞│
     │                   ▝▚                 ▚▖            ▞▘                ▗▀ │
-0.33┤                     ▚▖                ▝▄         ▗▞                 ▞▘  │
     │                      ▝▄                ▝▖       ▄▘                ▗▀    │
     │                        ▚▖               ▝▀▖   ▗▞                 ▞▘     │
-0.67┤                         ▝▄                ▝▚ ▄▘                ▗▀       │
     │                           ▚▄               ▄▞▄▖              ▄▀▘        │
     │                             ▀▄▖         ▗▄▀   ▝▄▖         ▗▄▀           │
-1.00┤                               ▝▀▚▄▄▄▄▄▞▀▘       ▝▀▀▄▄▄▄▄▄▀▘             │
     └┬─────────────────┬─────────────────┬─────────────────┬─────────────────┬┘
     0.0               1.6               3.1               4.7              6.3

10 · Multi-subplot canvases

Subplots are fully supported, including figure-level titles via canvas.suptitle(...).

[15]:
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.plot(backend="plotext").build(keep_colors=False))
Terminal dashboard
                   Signals                                Counts
     ┌─────────────────────────────────┐   ┌───────────────────────────────────┐
 1.00┤ ▞▞ sin(x)                    ▗▞▀│7.0┤ ██ count  █      ██▘██            │
     │ ▞▞ cos(x) ▄          sin(x) ▞▘  │   │ ▞▞ points █      █████count       │
     │   ▝▖       ▚               ▗▘   │   │██▗█████████      █████            │
 0.67┤   ▐▝▖       ▌        cos(x)▘    │5.8┤████████████      █████points      │
     │  ▗▘ ▝▖      ▐            ▗▘     │   │████████████      █████   ▖     ▖  │
 0.33┤ ▗▘   ▌       ▚           ▞      │4.7┤████████████      █████████████████│
     │ ▌    ▝▖       ▌         ▗▘      │   │████████████      █████████████████│
     │▗▘     ▐       ▚        ▗▘       │   │████████████      █████████████████│
 0.00┤▌       ▌       ▚       ▐       ▗│3.5┤████████████      █████████████████│
     │        ▝▖       ▌     ▗▘      ▗▘│   │████████████      █████████████████│
     │         ▚       ▐     ▌       ▞ │   │████████████      █████████████████│
-0.33┤          ▚       ▚   ▞       ▗▘ │2.3┤█████████████████ █████████████████│
     │          ▝▖      ▝▖ ▗▘      ▗▘  │   │██████████████▝██ █████████████████│
-0.67┤           ▚       ▐ ▌       ▌   │1.2┤█████████████████ █████████████████│
     │            ▚▖     ▗▞       ▞    │   │█████████████████ █████████████████│
     │             ▚▖   ▗▞ ▀▖   ▗▞     │   │█████████████████ █████████████████│
-1.00┤              ▝▙▄▟▘   ▀▚▄▄▀      │0.0┤█████████████████ █████████████████│
     └┬───────┬───────┬───────┬───────┬┘   └──┬─────┬─────┬─────┬─────┬─────┬──┘
     0.0     1.6     3.1     4.7    6.3      0.0   1.0   2.0   3.0   4.0   5.0
value                 x                 value               bin

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.

[16]:
data = np.arange(1, 26).reshape(5, 5)

canvas, ax = Canvas.subplots()
ax.add_imshow(data)
ax.set_title("Matrix-style imshow")
ax.set_xlabel("column")
ax.set_ylabel("row")

print(canvas.plot(backend="plotext").build(keep_colors=False))
                               Matrix-style imshow
 ┌─────────────────────────────────────────────────────────────────────────────┐
1┤█                  █                  █                  █                  █│
 │                                                                             │
 │                                                                             │
 │                                                                             │
2┤█                  █                  █                  █                  █│
 │                                                                             │
 │                                                                             │
 │                                                                             │
3┤█                  █                  █                  █                  █│
 │                                                                             │
 │                                                                             │
 │                                                                             │
4┤█                  █                  █                  █                  █│
 │                                                                             │
 │                                                                             │
 │                                                                             │
5┤█                  █                  █                  █                  █│
 └┬──────────────────┬──────────────────┬──────────────────┬──────────────────┬┘
  1                  2                  3                  4                  5
row                                  column

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.Rectangle

  • matplotlib.patches.Circle

  • matplotlib.patches.Polygon

  • matplotlib.patches.Ellipse

  • many other patch types that expose a usable matplotlib path

That is enough for many annotations, regions of interest, and geometric callouts.

[17]:
canvas, ax = Canvas.subplots()
ax.add_patch(
    mpatches.Rectangle(
        (0.2, 0.2), 1.3, 0.7, fill=False, edgecolor="yellow", label="window"
    )
)
ax.add_patch(
    mpatches.Circle((2.2, 1.6), 0.45, fill=False, edgecolor="cyan", label="sensor")
)
ax.add_patch(
    mpatches.Polygon(
        [[3.0, 0.5], [3.8, 1.2], [3.4, 2.0]],
        fill=True,
        facecolor="green",
        label="region",
    )
)
ax.add_patch(
    mpatches.Ellipse(
        (2.8, 1.0), 0.8, 0.5, fill=False, edgecolor="white", label="ellipse"
    )
)
ax.set_xlim(0, 4.5)
ax.set_ylim(0, 2.5)
ax.set_title("Supported patch types")
ax.set_legend(True)

print(canvas.plot(backend="plotext").build(keep_colors=False))
                                Supported patch types
    ┌──────────────────────────────────────────────────────────────────────────┐
2.50┤ ▞▞ window                                                                │
    │ ▞▞ sensor                                         window                 │
    │ ▞▞ region                                                                │
2.08┤ ▞▞ ellipse                     ▄▄▀▀▀▀▚▄▖          sensor                 │
    │                             ▗▞▀        ▝▚▄        region                 │
    │                            ▗▘             ▚       ellipse                │
1.67┤                            ▐              ▐         ▐████▙▖              │
    │                            ▝▖             ▞         ███████▖             │
1.25┤                             ▝▄          ▄▞ ▄▄▄▄    ▟████████▖            │
    │                               ▀▚▄▄▄▄▄▄▄▄▄▞▀    ▀▚▄▄██████████▘           │
    │                                       ▛           █▜██████▛▀             │
0.83┤   ▐▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▜              ▚▄▖        ▗▄▞████▀▘               │
    │   ▐                    ▐                ▝▀▀▀▀▀▀▀▀▘███▛▘                  │
    │   ▐                    ▐                        ▟██▀                     │
0.42┤   ▐                    ▐                        ▀▘                       │
    │   ▐                    ▐                                                 │
    │   ▝▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀                                                 │
0.00┤                                                                          │
    └┬─────────────────┬──────────────────┬─────────────────┬─────────────────┬┘
    0.0               1.1                2.2               3.4              4.5

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.

[18]:
# Interactive rendering of the same canvas
canvas.show(backend="plotly")

Data type cannot be displayed: application/vnd.plotly.v1+json

Data type cannot be displayed: application/vnd.plotly.v1+json

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') and set_yscale('symlog')

  • set_aspect(...) with a best-effort terminal plotsize approximation

  • add_colorbar(...) as a note-style value summary for matrix/image output

[19]:
x = np.linspace(-20, 20, 161)

canvas, ax = Canvas.subplots()
ax.plot(x, x**3, color="cyan", label="x^3")
ax.set_title("Symlog example")
ax.add_caption("caption text")
ax.set_xscale("symlog")
ax.set_yscale("symlog")
ax.set_aspect("equal")
ax.set_legend(True)

print(canvas.plot(backend="plotext").build(keep_colors=False))
        Symlog example | caption text
    ┌──────────────────────────────────┐
 3.9┤ ▞▞ x^3                        ▗▄▛│
    │                             ▄▛▀  │
 2.6┤                          ▗▟▀▘    │
    │                        ▗▞▀       │
 1.3┤                      ▗▛▘         │
 0.0┤                 ▄▄▄▄▀▘           │
    │           ▗▄▀▀▀▀                 │
-1.3┤         ▗▟▘                      │
    │       ▄▞▘                        │
-2.6┤    ▗▄▛▘                          │
    │  ▄▟▀                             │
-3.9┤▟▀▘                               │
    └┬───────┬────────┬───────┬───────┬┘
   -1.32   -0.66    0.00    0.66   1.32

[20]:
heat = np.arange(1, 26).reshape(5, 5)

canvas, ax = Canvas.subplots()
ax.add_imshow(heat)
ax.add_colorbar(label="intensity")
ax.set_title("Matrix + colorbar note")

print(canvas.plot(backend="plotext").build(keep_colors=False))
                    Matrix + colorbar note | intensity: 1..25
 ┌─────────────────────────────────────────────────────────────────────────────┐
1┤█                  █                  █                  █                  █│
 │                                                                             │
 │                                                                             │
 │                                                                             │
2┤█                  █                  █                  █                  █│
 │                                                                             │
 │                                                                             │
 │                                                                             │
3┤█                  █                  █                  █                  █│
 │                                                                             │
 │                                                                             │
 │                                                                             │
4┤█                  █                  █                  █                  █│
 │                                                                             │
 │                                                                             │
 │                                                                             │
5┤█                  █                  █                  █                  █│
 └┬──────────────────┬──────────────────┬──────────────────┬──────────────────┬┘
  1                  2                  3                  4                  5
row                                  column

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)

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.

[21]:
canvas.show(backend="plotly")

Data type cannot be displayed: application/vnd.plotly.v1+json

Data type cannot be displayed: application/vnd.plotly.v1+json

[22]:
x = np.linspace(0, 2 * np.pi, 60)

canvas, ax = Canvas.subplots()
ax.plot(x, np.sin(x), color="cyan", label="sin(x)")
ax.set_title("Saved terminal figure")
terminal_fig = canvas.plot(backend="plotext")

output_path = Path("plotext_output.txt")
terminal_fig.savefig(output_path)
print(output_path.read_text().splitlines()[0])
                                Saved terminal figure

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.

[23]:
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, ax = Canvas.subplots()
    ax.plot(x, np.sin(x + phase), color="cyan", label="sin(x + phase)")
    ax.plot(x, np.cos(x + phase), color="yellow", label="cos(x + phase)")
    ax.set_ylim(-1.2, 1.2)
    ax.set_title(f"Animated phase = {phase:.2f}")
    ax.set_legend(True)

    clear_output(wait=True)
    print(canvas.plot(backend="plotext").build(keep_colors=False))
    time.sleep(0.08)
                                Animated phase = 6.28
     ┌─────────────────────────────────────────────────────────────────────────┐
 1.20┤ ▞▞ sin(x + phase)                                                       │
     │ ▞▞ cos(x + phase) ▄▄▄                            sin(x + phase)     ▗▄▄▄│
     │   ▝▀▚▄    ▗▄▀▀       ▀▚▄▖                                        ▄▞▀▘   │
 0.80┤       ▀▚▖▞▘             ▝▚▄                      cos(x + phase)▞▀       │
     │      ▗▄▀▝▀▖                ▚▖                               ▗▀▘         │
     │     ▗▘    ▝▚▖               ▝▀▖                           ▗▞▘           │
 0.40┤   ▗▞▘       ▝▚                ▝▄▖                        ▞▘             │
     │  ▞▘           ▚▄                ▝▖                     ▄▞               │
 0.00┤▄▀               ▚                ▝▀▖                  ▞                ▗│
     │                  ▀▚                ▝▄▖              ▞▀                ▗▘│
     │                    ▚▄                ▝▖           ▄▞                ▗▞▘ │
-0.40┤                      ▚                ▝▚▖        ▞                ▗▄▘   │
     │                       ▀▚                ▝▄     ▞▀                ▗▘     │
     │                         ▀▄▖               ▀▚▗▄▀                ▄▀▘      │
-0.80┤                           ▝▄▖             ▗▄▘▄▖             ▗▄▀         │
     │                             ▝▀▄▄▖     ▗▄▄▀▘   ▝▀▄▄       ▄▄▞▘           │
     │                                 ▝▀▀▀▀▀▘           ▀▀▀▀▀▀▀               │
-1.20┤                                                                         │
     └┬─────────────────┬─────────────────┬─────────────────┬─────────────────┬┘
     0.0               1.6               3.1               4.7              6.3

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.