Skip to content

planviz

The figure library for OpenPlan Labs — grid maps, search animations, plan timelines and benchmark charts, in the Frontier palette, light and dark from the same call.

Planners not included. This draws results; it does not produce them.

import planviz

grid = [[0, 0, 0, 0], [0, 1, 1, 0], [0, 0, 0, 0]]   # truthy = blocked

ax = planviz.draw_search(
    expanded=[(0, 0), (1, 0), (2, 0), (2, 1)],
    frontier=[(0, 1), (2, 2)],
    path=[(0, 0), (1, 0), (2, 0), (2, 1), (2, 2)],
    grid=grid,
)
planviz.save(ax, "search.png")

The canonical search figure The canonical search figure

That figure is why the package exists. Every search algorithm has the same three sets — the nodes it expanded, the nodes on the frontier, and the path it returned — so the brand's three colours are that legend, and the three sets differ by shape as well as hue, which is the channel that survives greyscale printing and red/green colour blindness. Drawing it correctly by hand, in four repositories, twice each for light and dark, is the duplication this replaces.

The four contracts

Importing planviz changes no matplotlib state. The style is applied by use_style, or per-figure inside a style_context that restores rcParams on exit. A solver library can depend on this without repainting its user's notebook, and the test suite asserts it in a subprocess.

Every figure function takes dark: bool = False and an optional ax=. Light and dark variants come from one call; figures compose into panels.

Nothing is saved or shown for you. Functions return the Axes they drew on — or the Figure, for multi-panel figures. planviz.save(...) is the explicit write, and a test monkeypatches savefig to prove no figure function reaches for it.

The brand ships inside the wheel. planviz/tokens.py and planviz/styles/frontier.mplstyle are generated from openplan-labs/branding by scripts/sync_tokens.py, and a CI job fails the build if the committed copies have drifted. Nothing looks up a repository at runtime, and a figure rendered on an offline runner matches one rendered on a laptop.

No planner types

planviz imports no solver. Its vocabulary is:

Concept Shape
A grid 2-D array-like, grid[row][col], truthy means blocked, row 0 at the top
A path sequence of (row, col) pairs
Multi-agent paths {name: path}, or a sequence of paths named by index
A search's sets sequences of (row, col) cells
A search tree flat (node, parent, depth[, value]) records; parent == -1 at the root
A plan action names, or (label, start, duration[, row[, kind]]) steps
A benchmark series {name: {x: [samples over seeds]}}

Anything that can produce those can be plotted, which is what lets one library serve a MAPF solver, a GPU planner and a PDDL planner without any of them importing each other.

Where to start

  • Install — two dependencies, one optional extra.
  • Gallery — every figure, with the call that draws it.
  • Design rules — what the library enforces, and why.
  • API reference — every public symbol.
  • Migration — moving pymapf, cuplan or jupyddl onto this.