Changelogdocs
In developmentdocs
Version 0.7.0docs
New:
heatmap: a replacement for many uses ofimagefor displaying a grid of values through a colormap. It normalises the values onto the colormap rather than asking the caller to scale them first. A pre-computed grid of colours, or palette indices, or of values already scaled onto the range 0.0 to 1.0 can still be animage.- Finally, colour bars! Any way you want them!
colorbar: a gradient heatmap showing a range of colours.Direction: which way along the screen a colorbar runs. See thecolorbarsnote.- Vector colormaps and the plots that use them:
chroma: a colormap over the plane, turning a vector's direction into hue and its magnitude into brightness. Vectors may be spelled as complex numbers or as pairs.domain: a colormap for domain colouring, turning a complex number's phase into hue and its modulus into lightness. A zero is black and a pole is white, and a dark contour ring falls at every doubling of the modulus, so the rings count the order of a zero or a pole. Its scale is absolute rather than normalised, since the modulus is part of what it is showing.vfunction2: a colour field over a rectangle, sampling a vector-valued function of the plane.vrangescales the magnitudes into the unit disc, from zero to the largest by default, leaving the directions alone.cfunction2: a domain colouring over a rectangle of the complex plane, sampling a complex-valued function of one complex variable.- Calendar plots and support:
calendar: a heatmap of values observed on dates, drawing a block per month with a cell per day and wrapping the months into a grid.weeks: the same days as one unbroken strip, a column per week and a row per weekday, captioned with the months and years along the top. Awidthwraps a strip too long for the terminal onto further bands, each captioned again.DateSeries: the forms dated data arrives in, accepted bycalendar. A mapping from dates to values, separate sequences of dates and values, or one date standing in for the consecutive days from there. Dates may be spelled asdatetimedates or datetimes, NumPydatetime64s, or ISO 8601 strings.table: a grid of values, formatted into aligned columns and ruled. Takes a list of dicts, a dict of lists, or a 2d array, and sizes each column to what is in it.- A float shows four significant figures and a column of numbers aligns right so its digits line up, until a format or an alignment says otherwise, given for the whole table, per column, or per column name.
- Each of its eight rules---
toprule,midrule,rowruleandbottomruleacross,leftrule,indexrule,colruleandrightruledown---takes aRuleof its own, defaulting to a line above, a double line under the header and a line below. - Cells take colours one at a time, so a table shaded by its own values reads
as a heatmap whose numbers can still be read off exactly. See
the
tablesnote. TableData: the forms a grid of values arrives in, accepted bytable. A list of dicts, a dict of lists, or a sequence of rows or 2d array, the first two naming their columns and the last not.Rule: what a table draws between its cells---"skip"for nothing at all,"blank"for a row or column of space,"single"for a light line or"double"for a double one.Align: where a value sits in its cell,"left","center"or"right".- Box plots and candle plots:
boxes: a box plot, one box per group of samples. The box spans the first and third quartiles and is divided at the median, whiskers reach the furthest sample withinwhisker_iqrstimes the interquartile range of the quartiles, and every sample beyond them is drawn as a point. Takes raw samples, ragged groups allowed, and works the quartiles out itself.- Horizontal and vertical modes. Which is which? We label by the direction of the box.
- Outlined (unicode box-drawing characters) and filled (unicode eigths). Filled has slightly higher resolution but requires a solid background.
candles: a candlestick chart, one candle per period, its body spanning the opening and closing values and its wick reaching out of the body to the high and the low. Bodies are coloured by whether the period closed above or below where it opened, and land on an eighth of a character cell.Orientation: which way a mark lies, and so which way its value axis runs. Both take one, asbox_directionandcandle_direction.window: the interval of data a plot covers on each axis, and the rectangle of character cells it covers them with. Every 2d plot carries one, and it provides the conversions from data coordinates to the grids of dots and pixels the plots are drawn in.- A range given descending inverts its axis:
xrange=(1, 0)mirrors a plot left to right and a descendingyrangeturns it over. The plots that place points and sample functions did this already, by arithmetic rather than by design;histogram2now does it too, binning with its edges ascending and turning the counts around afterwards, so that the same data always lands in the same bin.
New examples:
axes_gallery.py: demonstrating different ways of attaching axes to some data.candlesticks.py: a simulated price series as candles, one column to a period.colorbars.py: colour scales, and the bars that stand for them.commit_heatmap.py: a year of commits to this repository as a strip of weeks.domain_coloring.py: six complex functions, painted onto their own input plane.phase_portrait.py: six planar vector fields, as colour fields.tables.py: a hyperparameter sweep reported in tables.globe.py: a spinning Earth. See theworld-mapsnote.world_map.py: great-circle flight routes in different projections.
Changed:
axesdraws each of its four sides independently. Every side takes aSide:"crop"for nothing at all,"pad"for a blank cell,"rule"for a line, or"label"for a line with ticks at its ends and its coordinate's limits outside it. Left unspecified, each coordinate the plot carries is labelled once, below it and to its left, and the remaining sides are ruled when the plot carries both coordinates and dropped when it carries only one. So a gradient with one coordinate is labelled along one side and left alone on the others.axestakes aLineStyle---LIGHT,HEAVY,ROUNDorDOUBLE---rather than aBoxStyle. The characters where its rules meet, and the ticks reaching out towards its labels, are derived from which sides are drawn rather than written down, which an eight-characterBoxStylecannot express.borderstill takes aBoxStyle, with all of its styles.axesno longer garbles the limits of a plot too narrow to hold them, or raises when such a plot is also given an axis name. The limits use the whole width available, including the gutter under the y labels, and are replaced by hashes if even that will not fit them, as a spreadsheet does. Axis names are truncated as before.axeswrites a title into the north side when that side is blank or ruled, and gives it a row of its own otherwise.axesrefuses a plot carrying no coordinates at all, rather than silently framing it.borderis for that.BoxStyleno longer offersLIGHTX,HEAVYXorLOWERX. They existed so that a border could carry the ticks anaxesneeded, and anaxesderives its own now.function2takes its colour scale asvrangerather thanzrange, the name the rest of the library already gave the interval of values a plot covers.vrangeunification and changes:- A
vrangeis a pair of numbers or nothing at all.bars,columns,calendarandweeksused to accept a single number too, meaning zero up to it; write(0, hi)instead.histogram,vistogramandhistogram2keep their ownmax_count, which is that shorthand under a name that says what it is counting. - A plot keeps the interval it settled on as
plot.vrange, one pair, rather than asplot.vminandplot.vmax. imagekeeps no interval, since its data is already colours or already scaled, so a colorbar for one has to be told the interval outright.- A
vrangethe caller wrote that covers no interval is an error, rather than quietly colouring everything at the bottom of the scale or dividing by something close enough to nothing. Every plot that measures its values against an interval says so the same way, whether that interval becomes a colour scale or a length along the screen:heatmapand the plots built on one,calendar,weeks,vfunction2,bars,columns,boxesandcandles. - An interval inferred from values that are all the same still puts them
all at the bottom of a colour scale, there being nothing else it could
mean. The plots that draw a position along the interval---
boxesandcandles---have nowhere to put them instead, and say so. - A value that is not a number is left out of an inferred interval, and comes
out at the bottom of the scale wherever it appears: at the bottom of the
colormap for a
heatmap, and as a bar or column of zero width forbarsandcolumns. - A sample that is not finite is left out of a
boxessummary, as a measurement that was not made, rather than shifting the quartiles or counting as a point beyond the whiskers. A group with no finite samples at all is an error.candlesinstead refuses a value that is not a number outright, a period with an unknown high having no candle to draw. texttakes awidth, aheightand analign. The width and the height are the least it may take, so a text plot can be held to a size larger than its text, and the alignment says where each line sits in the width---which is what atablecell is, and what it now uses.- The examples that had values on a colour scale draw them with
heatmap, rather than scaling them into the unit interval by hand first. The ones drawing colours, or palette indices for a discrete colormap, still useimage, which is what it is for. time_series_histogram.pygives its 2d histogram the colorbar it had a standing TODO for, along the foot of the panel so that all three panels stay the same width.
Fixed:
textdocumented its foreground colour argument under the wrong name.text("")is a plot of no rows rather than aValueErrorfrom taking the longest of no lines.text("\n"), which has one empty line, is still a plot of one row.hilbert's repr closes its parenthesis.- One value that is not a number no longer draws every bar of a
barsorcolumnschart full. It poisoned the largest value, and so the interval, and so every bar, which then saturated at the top of a scale that was itself not a number. colorbarsays what is wrong with a plot whose values are all the same, rather than reporting avrangecovering no interval against an argument the caller never wrote. Such a plot has one colour and no axis to label it along, so there is no bar to draw for it.plot.xrangeandplot.yrangehave moved onto the window, asplot.window.xrangeandplot.window.yrange.axesanddstack2accept any plot carrying a window, rather than a listed union of plot types.imageaccepts anxrangeand ayrange, so that an image can be given axes or overlaid on another plot. Without them it carries no coordinates, as before. An image with an odd number of pixel rows cannot be given them, since it half-fills its last character row.- Plots report their window in their reprs, rather than separately spelling out their dimensions and their ranges.
dstack2requires its plots to share one window---the same intervals in the same number of character cells---and refuses mismatches, plots without coordinates, and an empty stack with aValueError. It used to compare only the intervals, and to do so with assertions, which vanish underpython -O.function2shows the value of the function at the centre of each grid square, rather than at its lower left corner, so that the picture is no longer biased half a square towards the low end of each range. Withendpoints=Trueit still samples the ends of both ranges exactly.
Version 0.6.3docs
Documentation:
- Rebuilt library documentation using MkDocs and a modification of the terminal theme.
- The website is now versioned, one directory per release, published to the
gh-pagesbranch rather than committed to the repository. The site root redirects to the newest release, and page addresses have changed to suit: the API reference is now at/latest/api/, the quickstart at/latest/quickstart/, and so on. - The website now carries every released version, each at its own address, with a menu in the top bar for moving between them. The root redirects to the newest.
- Reorganise example page by topic.
New examples:
boids.py: 2D flocking simulation usingmp.lineto draw short directional segments andmp.rainbowfor coordinated coloring.doomfire.py: Vectorized implementation of the classic 1997 PSX Doom fire effect mapped through a 37-color palette usingmp.image.lorenz.py: Animated 3D Lorenz attractors racing side-by-side usingmp.scatter3,mp.line3, andmp.wrap.sorting.py: Various sorting algorithms racing in parallel, visualised withmp.columnsand dynamic layout wrapping.
Documented:
- The type aliases the modules introduce and then left out of the reference:
SeriesandSeries3,ColorLike, and the colormap types. - The
xaxis,yaxisandzaxisseries, andunscii16.bitmaps.
Fixed:
doomfire.pyseeds its randomness, so the example draws the same fire on every run, as the other stochastic examples do.
Version 0.6.2docs
Changed:
parse_colorscan now standardise scalar and RGB arrays of a requested shape and apply a colormap.imageandanimationuse it as their shared colour-input path; custom colormaps may accept arrays of any shape provided they return[h,w,3]or[t,h,w,3].
Fixed:
- Text, titles, and labels reject terminal control characters instead of emitting them verbatim. Raw ANSI styling was never structurally supported: its invisible bytes were counted as character cells, breaking composition, differential redraws, and image rendering.
function2values outsidezrangeandhistogram2counts abovemax_countsaturate at the colour-scale endpoints instead of wrapping around with some colormaps. All-zero histograms no longer divide by zero, and an explicitmax_countmust be positive.imagerejects arrays with non-RGB channel counts instead of producing malformed terminal colour sequences or failing later during image rendering.
Examples:
- Attribute examples to their designers.
- Do you have a cool, standalone matthewplotlib example? Consider sharing!
- Streamline descriptions on example page.
quickstart2.pyandquickstart3.pyamplitude fix.
New examples:
chromatic_flow.py: a periodic incompressible velocity field whose custom colormap turns vector direction into hue and speed into brightness.three_body.py: three equal masses integrated under Newtonian gravity in a shared figure-eight orbit, with fading trails.
Version 0.6.1docs
New:
savegiftakespaletteandcolors, choosing how an animation's colours are fitted into the palettes a gif stores instead of a colour per pixel.palette='unified'(the default) builds one palette for the whole animation, so a colour is the same colour in every frame.palette='per-frame'gives each frame its own, spending the whole budget on each frame separately.colors(2 to 256, default 256) is how many colours a palette may hold. Fewer means a smaller file.
Changed:
- Gifs are saved in the colours the plots were drawn in, for an animation of 256 colours or fewer. Previously each frame was reduced on its own, and to far fewer colours than a gif allows, which banded smooth colourmaps and let still content change colour from frame to frame. Animations with more colours than the budget are now reduced once for the whole animation instead.
- Colourful gifs are larger as a result.
colors=32asks for small files back explicitly. See thegif-sizenote for the measurements.
Fixed:
savegifno longer ghosts an animation with a transparent background. Frames after the first kept the pixels of the frames before them, so moving content smeared over everywhere it had been.
Examples:
teapot.pyorbits once overnum_framesrather than once every two seconds, so a saved gif loops at any length. The showcase gifs are regenerated.
Version 0.6.0docs
New:
mp.line(series, ...): line plots, connecting the points of each series in order, drawn with braille dots likemp.scatter.- Variable
thicknessmeasured in dots, round caps. - NaN breaks lines.
- Colors interpolate along each segment.
mp.line3(series, ...): the same for points in space, seen from a camera configured as formp.scatter3.
Changed:
mp.scatterandmp.linemap data onto dots the same way, so a scatter's points move by up to one dot: the limits of the data now land on the centres of the outermost dots rather than the outer edges of the outermost bins.parse_rangeignores non-finite values, and gives a range to data that reaches no distance: a constant series now reports the range it is drawn in rather than one of zero width.- 3d projection moves out of
datainto a new module,camera, which gainsperspectivealongsideview_matrixand the twoproject3*functions. - Color specs move out of
dataintocolors, asparse_colors.
Fixed:
mp.scatter3is no longer a subclass ofmp.scatter.- As a consequence,
mp.axesno longer takes it (the axis labels would have been based on the projection anyway). - Floats in color arays now have range [0.0, 1.0], as was already the case for single float colors.
- Previously, floats in color arrays were interpreted in [0.0, 255.0].
- Improved color-parsing error handling and error messages.
New examples:
vaporwave.pyexample: a wireframe landscape scrolling under a banded sun, terrain showcasingmp.line3.lines.pyexample: showcasingmp.line, two loss curves, one of them measured sparsely and with a stretch missing, and the same spiral drawn with four widths of pen.starburst.pyexample: a turning rose of rays whose stroke swells from one dot to six and back.
Version 0.5.0docs
New:
mp.tstack(*plots): animations as values. The third stacking operation, arranging plots in time rather than across the screen. Supportslen, indexing and slicing (a[0],a[10:20],a[::-1]),mapfor applying a combinator to every frame,playfor showing it in the terminal, andsavegif. Frames are padded to a common size, so an animation cannot change shape while it plays.mp.animation(array): atstackstraight from an array with a time axis, taking whatmp.imagetakes with a leading frame index.mp.animate(): a context manager that runs an animation loop.anim.update(plot)writes one frame and returns the string it wrote. Optionally caps the frame rate (fps=), keeps the frames (record=True, readable asanim.frames), and ends quietly on Ctrl-C (stop_on_interrupt=True). Reports the rate actually achieved asanim.achieved_fps.anim.print(...): print a line above a running animation instead of through it. Also available as a stream,anim.out, forprint(file=...),logging.StreamHandler, or redirectingsys.stdoutfor the block.mp.tstack(...).savegif(f, fps="achieved")encodes a recorded animation at the frame rate it really ran at, rather than the one that was requested.life.pyexample: Conway's Game of Life, demonstrating differential redraw.quickstart3.pyexample:quickstart2.pywith the animation loop handed tomp.animate.boing.pyexample: the Amiga Boing Ball, built withmp.animationfrom a computed array of frames, spinning by palette cycling as the original did, with the cycling palette shown underneath it.- A compatibility page (
pages/compatibility.md): every escape sequence the library emits, the terminal behaviours it relies on, the glyph blocks it draws with, and which terminals are actually tested. terminal_test.pyexample: does your terminal render matthewplotlib correctly? Four stages exercising every escape sequence the library can emit, each saying what it should look like. Measures the terminal's width and draws to it, so that the last stage puts a plot against the right margin.
Change:
mp.save_animation(plots, filename, ...)is retired in favour ofmp.tstack(*plots).savegif(filename, ...).teacher_student.pytakes a--log-everyargument, and logs its loss withanim.print.- Animated redraws now speak only VT100, apart from the SGR colours.
CHA(absolute column) became a carriage return plus a cursor forward,CNL(next line) a carriage return plus a cursor down, andECH(erase character) written spaces. The screens are identical -- across every example snapshot only the byte counts moved -- and the point isCHA, whose old use depended on it cancelling a deferred wrap, which is the thing terminals disagree about most. A full repaint now costs less rather than more; a sparse diff costs one byte more. See theescape-vocabularynote.
Fix:
dashboard.pyno longer raisesNameErrorwhen asked to save an unbounded run.axesno longer paints theylabeldown the plot's right-hand border when the y tick labels are narrower thanypad + 1. The tick gutter now widens to make room. An absentylabelno longer erases that border either.- Correct the Python version classifiers, which still advertised 3.10 and 3.11 after 0.4.0 raised the requirement to 3.12.
Dev:
tests/test_exports.pychecks that everythingplots,colormapsandanimationsdefine is reachable asmp.something, deriving the expectation from the modules rather than from a list, so adding a feature does not mean editing a third file.- Add module docstrings for
core,colorsanddata, so every module now introduces itself in the API reference. - Escape sequences are now tested against a real terminal (a tmux pane, see
tests/test_terminal.pyandtests/tmux.py) rather than a hand-written emulator, which retires the emulator and makes tmux a development dependency. See theterminal-test-backendnote. - The example smoke tests are replaced by snapshot tests. Every example is
replayed into a real terminal print by print and compared against a golden in
tests/goldens/, cell by cell in both glyph and colour, along with the byte cost of each print and a digest of the image it saved. Regenerate withmake goldens. See theexample-snapshot-testsnote. life.pyseedsnp.random.seedrather thannp.random.default_rng, whose stream NumPy does not guarantee across releases. Its initial board, andimages/life.gif, change accordingly.TestEmittedVocabularypins the set of escape sequences the renderer is allowed to emit, over every path that emits any. It is the executable form of the compatibility page, so the page cannot go quietly out of date.
Version 0.4.0docs
New:
- Add differential redraw for animated plots:
print(plot - prev)repaints only the cells that changed. SubtractNonefor the first frame.
Change:
- Every string the library returns is now shaped for a plain
print. In particularclearstr(-plot) must no longer be printed withend="".
Fix:
clearstr(-plot) erases only the plot's own rows, rather than everything below it on screen, and handles a plot with no rows.- Stop passing the deprecated
modeargument to Pillow, which will be an error in Pillow 13. - Declare the Python version actually required (3.12, for
typealiases); the package never supported the 3.10 it claimed.
Version 0.3.8docs
Fix:
- Make automatic
wraplayout work without an attached terminal.
Version 0.3.7docs
Fix:
- Fix BIDS colormaps (magma, inferno, plasma, viridis) returning wrong dtype.
Dev:
- Add unit test suite and
make testtarget. - Add integration tests for all examples.
- Add
pytestto dev dependencies. - Add
tyroCLI argument parsing to long-running examples.
Version 0.3.6docs
Fix:
- Fix operator precedence bug in
isblank/isnonblank(affecteddstackoverlays). - Fix
axes.__repr__returning"border(...)"instead of"axes(...)". - Accept
listas a valid color input inparse_color. - Fix
save_animationplaying GIFs twice instead of once whenrepeat=False. - Fix mypy errors.
Version 0.3.5docs
New:
- Diverging colormaps divreds, divgreens, divblues.
Fix:
- Fix bug in bar chart layout.
Version 0.3.4docs
New:
- Per-column and per-bar colours in column and bar plots.
Fix:
- Fix bug in column/bar spacing implementation.
Version 0.3.3docs
New:
dstack2for stacking data, extend axes to more datatypes.teacher_student.pyexample
Fix:
- Missing title parameter from border.
Notes:
- Plausibly
dstack2should be the default anddstackshould be removed.
Version 0.3.2docs
New:
- Transpose parameter for wrap.
Version 0.3.1docs
New:
- Axes subplot type. Takes a scatter plot or function2 plot as input, and adds axes with labels and ticks. Basic API.
- Animated version of quickstart example.
Version 0.3.0docs
Breaking changes:
- scatter and scatter3 take xs, ys, (zs), and color as series tuples in positional arguments.
- removed function plot type (since scatter is now much easier to use).
New:
- scatter and scatter3 accept cs, an array of colors (one for each point), and plot using them, using weighted averaging to combine plots.
- scatter and scatter3 now accept multiple series at once.
- special series for X/Y/Z axes.
- some new examples (deigned by Gemini 2.5 pro): voronoi, dashboard, mandelbrot.
Version 0.2.1docs
Fix:
- Regenerate documentation.
- Update version number properly.
Version 0.2.0docs
Breaking changes:
- Various argument name changes, especially for colors.
- Inverted
cybercolormap. - Move
plots.border.Styletocore.BoxStyle.
New:
- Configurable background colour for image rendering.
- 3d scatterplot.
- Discrete colourmaps are now cyclic.
- New discrete colourmaps
tableau,nouveau. - New border styles.
- Export animations as GIFs.
- New configuration options for bar/column sizes.
Internal:
- Refactor backend to use numpy arrays rather than nested lists.
Version 0.1.2docs
Breaking changes:
- Change operators used for shortcuts.
- Rename
fimagetofunction2.
New:
- New plot types:
bars,columns,histogram,vistogram,histogram2,function. - More documentation.
- Generated markdown documentation.
- Additional examples.
Dependencies:
- Make example dependency on
scikit-learnexplicit.
Version 0.1.1docs
New:
- Add type annotations.
Dependencies:
- Add
mypyas a dev dependency. - Remove dependency on
unscii(bundle the specific version of the font we want).
Internal:
- Refactor from long single-file script to multi-file library.
Version 0.1.0docs
Much unstructured development.