Terminal compatibility

Matthewplotlib draws plots by writing bytes to a terminal. What you see depends on what your terminal does with them. This page is the complete list of the kinds of bytes the library produces and what different terminals might do with them.

The library produces three kinds of byte sequences:

Movement/erasure control codes are only used for plot redrawing/animation. If you print a plot and never animate it, you are only using colour, glyphs, and newlines, which should work essentially everywhere. Otherwise, we try to keep the set of escape sequences minimal to maximise compatibility.

The library produces strings for the terminal via pure functions that don’t check the state of the terminal. It looks at the terminal in only two places, and neither one changes a byte of what is written: wrap reads the terminal width to choose a grid layout, and animate reads the terminal height to decide whether your plot is too tall to animate and you should be warned. Therefore:

Glyphs

The plots are drawn with characters, so the font matters as much as the terminal. The library uses four blocks:

Range Block Used by
ASCII tick labels, titles
U+2500–U+257F Box Drawing border, axes
U+2580–U+259F Block Elements image (half blocks), bars and columns (eighth blocks), blocky borders
U+2800–U+28FF Braille Patterns scatter, hilbert, dotted borders

(The library also allows you to render arbitrary glyphs through text plots, those glyphs are your responsibility.)

Braille is the one to check. It is the densest and the least likely to be in an older bitmap font, and a font that lacks it turns a scatter plot into a field of replacement boxes. Most modern terminal fonts have it.

The library generally operates on the following assumption about how these characters are rendered:

If glyphs are the problem rather than the terminal, saveimg renders the same plot to a PNG through an embedded pixel font, with no dependency on your font at all.

On the roadmap:

Colour

The library emits the following control codes for controlling the forground/background colour of terminal glyphs.

Sequence Name Effect Used for Standard
ESC [ 39 m / ESC [ 49 m SGR Default foreground / background Returning one channel to default. ECMA-48
ESC [ 38;2;r;g;b m SGR 24-bit foreground Colour. ISO 8613-6
ESC [ 48;2;r;g;b m SGR 24-bit background Colour. ISO 8613-6

In particular, the library always emits 24-bit colour control codes. On a terminal with fewer colours the usual behaviour is to approximate to the nearest available, which might still be readable. Some very old terminals might ignore the sequence instead and render in the default colour, which is legible but flat.

On the roadmap:

Movement/Erasure

For standard plotting, the library relies on only newlines.

Sequence Name Effect Used for Standard
LF (\n) Next row, scrolling at the bottom Line breaks ASCII

During plot erasure/redrawing, such as for animation, the library emits the following escape sequences in different contexts. A terminal that handles these eleven things the way we expect should support animation.

Sequence Name Effect Used for Standard
CR (\r) Column 0 Reaching a known column from an unknown one ASCII
LF (\n) Next row, scrolling at the bottom Growing a plot taller ASCII
ESC [ n A CUU Cursor up n rows Animation: reaching the frame above VT100
ESC [ n B CUD Cursor down n rows Animation: reaching a later row VT100
ESC [ n C CUF Cursor forward n columns Animation: skipping unchanged cells VT100
ESC [ n D CUB Cursor back n columns Animation: stepping back within a row VT100
ESC [ 2 K EL Erase the whole line Clearing a plot, and rows a shrinking plot gave up VT100
ESC [ 0 m SGR Reset all attributes Ending a coloured run VT100
ESC [ 39 m / ESC [ 49 m SGR Default foreground / background Returning one channel to default. ECMA-48
ESC [ 38;2;r;g;b m SGR 24-bit foreground Colour. ISO 8613-6
ESC [ 48;2;r;g;b m SGR 24-bit background Colour. ISO 8613-6

Note that recognising a sequence is not the same as agreeing what it does. These are the behaviours the library counts on.

Three things we don’t use, since terminals genuinely diverge in how they are handled:

Because the library only ever moves relative to the cursor and never addresses the screen absolutely, output composes with whatever else is on screen. It does not own the display.

Legacy:

Screen size

The library produces strings independently of the terminal, as above. These strings generally assume the rendered plot will fit neatly onto the screen. Otherwise, line wrap and scrollback may upset the effect of the control sequences. Specifically:

On the roadmap:

Supported terminals

At the moment, we only officially guarantee support for tmux.

Terminal Status How
tmux 3.5a Verified continuously The test suite drives a real tmux pane. Every behaviour listed above is asserted there, and every example is snapshotted frame by frame—glyph, colour, cursor and scroll position of every cell.
zmx 0.6.0 (ghostty-vt 1.3.2-dev) Spot-checked Two cases run by hand and read back with zmx history --vt: a plot narrowing and shortening, which leaves no columns behind; and a plot the full width of a 160-column pane animated through six differential frames, which lands where it should. Not automated, so nothing detects a regression.
Alacritty Used, not tested The author runs it. No evidence beyond that.

Worth knowing if you use a multiplexer or a session-persistence layer: those parse the library’s output with their own terminal emulator and then repaint the real terminal themselves. tmux and zmx are both in that category — zmx by way of ghostty’s VT engine. So for a reader inside one of them, the compatibility question is about the multiplexer and not about the terminal underneath it, which is why testing against tmux is worth as much as it is. Two of the three rows above are such layers, and they are two independent implementations, which is the useful part: they agree.

If you have a terminal that is not listed above and you see visual distortion, please report it. There is an example that exercises every escape sequence on this page, in the situations where they are hardest, and says what each stage should look like so that you can judge it:

python examples/terminal_test.py

It measures your terminal’s width and draws to it, so that stage 4 puts a plot against the right margin — the deferred-wrap case above. That is the one stage testing something your terminal does not already do constantly, so it is the one to watch. (If it cannot measure the width, because output is redirected somewhere that is not a terminal, it says so and stage 4 tests nothing.)

If a stage misbehaves, please open an issue saying which one, along with your terminal, its version, and echo $TERM.


  1. The axes wraptest probes are:

    • whether the terminal defers the wrap at all, or wraps immediately;
    • which operations clear the flag. STD-070 says cursor movement (CUU, CUD, CUF, CUB), cursor positioning (CUP, HVP), the control characters BS, HT, CR and LF, and the erase, delete and insert operations (ECH, DCH, ICH) all should. In practice which ones actually do varies;
    • what column the terminal reports while the flag is set. tmux reports the width rather than width − 1 — pinned by test_wrap_is_deferred_at_the_right_margin — and a model that reports width − 1 computes every subsequent relative move one column off.

    Even the hardware disagrees: wraptest reports that the VT100 diverges from STD-070 considerably, the VT220 follows it to the letter, and the VT510 differs again; among emulators only recent xterm is described as faithful.↩︎