No description
  • Python 99.2%
  • Shell 0.8%
Find a file
Paul Burton 3b2d677ad7 Add bin/blocky launcher to run from a checkout without installing
Resolves its own location (following symlinks), walks up to the repo root that
holds the blocky package, puts it on PYTHONPATH and runs the CLI.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 16:28:02 +01:00
bin Add bin/blocky launcher to run from a checkout without installing 2026-06-20 16:28:02 +01:00
blocky Add theme system: swappable .theme files with default and sharp built-ins 2026-06-20 16:24:56 +01:00
examples Add theme system: swappable .theme files with default and sharp built-ins 2026-06-20 16:24:56 +01:00
tests Add theme system: swappable .theme files with default and sharp built-ins 2026-06-20 16:24:56 +01:00
.gitignore Initial blocky implementation: parser, layout, routing, SVG/PNG render 2026-06-20 10:19:11 +01:00
pyproject.toml Add theme system: swappable .theme files with default and sharp built-ins 2026-06-20 16:24:56 +01:00
README.md Add bin/blocky launcher to run from a checkout without installing 2026-06-20 16:28:02 +01:00

blocky

Generate attractive block diagrams from a simple text format.

blocky turns a small, human-friendly text description into a clean SVG (or PNG) block diagram. It takes care of the tedious, easy-to-get-wrong parts — consistent sizing and spacing, sensible default colours, nesting, arrow routing that avoids overlaps — so that a few lines of input produce something that looks designed rather than dumped.

diagram pipeline {
    block input  "Input"
    block parse  "Parser"
    block layout "Layout"
    block render "Renderer"
    block output "SVG / PNG"

    input -> parse
    parse -> layout
    layout -> render
    render -> output
}

minimal example

Why it looks good by default

  • Consistent layout & sizing. Blocks are sized to their labels and children with uniform padding; a layered layout assigns them to ranks along a flow direction and orders them to minimise crossings.
  • Consistent spacing. Gaps between blocks are uniform and widen automatically where more arrows need to pass between them.
  • Sensible default colours. Every block gets a coordinated fill, border and readable text colour with no configuration. Distinct classes are auto-assigned from a calm palette.
  • Grid snapping. Every edge snaps to a configurable grid (default 10×10) so the diagram lines up crisply.
  • Arrow routing. Arrows route orthogonally with right-angled bends around blocks, and are pushed into separate lanes so they don't overlap each other.

Install

pip install -e .          # SVG output, no dependencies
pip install -e '.[png]'   # add PNG output (cairosvg)

Usage

Run it straight from a checkout with the bundled launcher (no install needed):

./bin/blocky diagram.blk                 # finds the package relative to itself

Or, once installed (pip install -e .), use the blocky command:

blocky diagram.blk                       # -> diagram.svg (last diagram)
blocky diagram.blk -o out.svg
blocky diagram.blk -o out.png            # PNG inferred from extension
blocky diagram.blk -f png -o out.png
blocky diagram.blk -d core               # render a specific named diagram
blocky diagram.blk --list                # list diagrams in the file
blocky diagram.blk -g 8                  # override the grid size
blocky diagram.blk --theme sharp         # render with a different theme

If you don't name a diagram, the last one in the file is rendered — which is usually the largest, composed from the earlier ones.

The format

The format is line oriented and forgiving. A whole diagram can be a handful of block and arrow lines; everything else is optional.

Blocks

block id                      # id is also the label
block id "A nicer label"
block id : someclass "Label"  # id : class "label"

block group "Container" {     # blocks nest; containers size to fit children
    block child1 "Child 1"
    block child2 "Child 2"
    child1 -> child2          # arrows can live inside a body, near their blocks
}

A block with children is a container: it is sized automatically to wrap them with padding and a band at the top for its own label.

Labels and descriptions

Long labels wrap automatically; use \n to force a line break. A block can also carry a desc: shown beneath the label in a smaller, muted font — handy for a one-line note like a technology or responsibility. This works on leaves and containers alike.

block db : data "Primary Database" {
    desc: "PostgreSQL 16\nmulti-AZ, read replicas"
}

descriptions example

Arrows

a -> b          # forward
a <- b          # backward
a <-> b         # bidirectional
a -- b          # plain line, no heads

a -> b : bus            # give it a class
a -> b : bus "payload"  # ...and a label

Endpoints can reference any block by id. A uniquely-named nested block can be referenced by its bare id; otherwise use a dotted path (group.child1).

Classes

Classes let you style a whole category of block or arrow once. display is the name shown in a legend.

blockclass cpu {
    display: "CPU core"
    fill:    #4c78a8     # everything else (border, text colour) is derived
    text:    #ffffff     # ...but can be overridden
}

arrowclass bus {
    display: "System bus"
    color:   #2f4b66
    width:   2.4
    style:   dashed      # solid | dashed | dotted
}

Block class properties: display, fill, stroke, text, stroke_width, radius, legend (off to hide from auto legends). Arrow class properties: display, color, width, style, legend.

Any property can also be set inline on a single block or arrow with the same key: value syntax inside a { ... } body.

Constraints

Constraints are optional hints honoured by the layout when set:

a left-of b      # ordering along / across the flow
a right-of b
a above b
a below b

a near b         # pull two blocks closer
a far b          # push them apart
a near b 20      # ...with an explicit gap in pixels

Flow direction

diagram d {
    flow: down    # right (default) | down
    ...
}

Composition

Define a small diagram, then drop it into a larger one as a self-contained unit. The inner diagram is laid out on its own and then placed as a block:

diagram node {
    block soc "SoC" { block cpu : logic "CPU"  block ram : mem "RAM"  cpu -> ram }
    block flash : mem "Flash"
    soc <-> flash
}

diagram board {
    use node as left
    use node as right
    block switch "Switch"
    left <-> switch
    right <-> switch
}

composition example

Legend

legend auto                 # list every block & arrow class actually used
legend off                  # (default) no legend

legend manual "Title" {     # list exactly what you choose
    block cpu
    arrow bus
}

Themes

A theme sets the defaults for a whole diagram — the palette used to auto-colour classes, the default block and arrow styling, the font, and whether corners are rounded. Two themes ship built in:

  • default — the calm palette with softly rounded corners (what you see throughout this README).
  • sharp — the same palette and styling with square corners on blocks and arrow bends.

Select a theme in the file or on the command line; the CLI wins:

theme sharp                 # in the .blk file (top level)
blocky diagram.blk --theme sharp
blocky diagram.blk --theme path/to/custom.theme

sharp theme

Themes are plain text files. A custom theme can include a built-in one and override just what it needs:

# ocean.theme
include: default
palette:    #2a6f97, #468faf, #61a5c2, #89c2d9, #014f86
block.fill: #e8f1f5
arrow.color: #014f86

Theme properties: palette, font_family; block.fill, block.stroke, block.text, block.stroke_width, block.radius; container.fill, container.stroke, container.text; arrow.color, arrow.width, arrow.corner_radius. Set block.stroke/block.text to auto to derive a coordinated border / legible text colour from the fill, and set the radii to 0 for square corners. Class and inline properties always override the theme. The built-in themes live in blocky/themes/.

Examples

The examples/ directory contains the source .blk files and their rendered output:

Example Shows
minimal.blk the smallest useful diagram
soc.blk classes, nesting, arrow classes, auto legend
compose.blk reusing a sub-diagram, named selection
webapp.blk flow: down, spacing, a manual legend
scheduler.blk arrow labels, dense fan-out, lane separation
services.blk descriptions and wrapped multi-line labels

soc example scheduler example webapp example

Library use

from blocky import parse_file, render_document, write_output

doc = parse_file("diagram.blk")
svg = render_document(doc, name=None)   # None -> last diagram
write_output(svg, "out.png", "png")

How the layout works

  1. Compose. use directives expand into nested container blocks holding a copy of the referenced diagram, with its internal arrows carried along.
  2. Size, bottom-up. Leaf blocks are sized to their label; containers are sized to wrap their laid-out children plus a label band.
  3. Layer. At each nesting level, blocks are assigned to ranks along the flow axis (from arrow direction and ordering constraints) and ordered within each rank by a barycenter heuristic to reduce crossings.
  4. Align. Connected blocks in adjacent ranks are pulled toward a common cross-axis position (via isotonic projection that respects order and spacing) so that single connections come out as straight lines.
  5. Place & snap. Ranks are spaced with consistent gaps that widen for busy channels, then every edge is snapped to the grid.
  6. Route. Each arrow leaves and enters its blocks with a straight stub (clearing any container border it crosses) so it always meets an arrowhead head-on. Ports sharing a side are fanned out with even spacing, and where two blocks' ranges overlap their ports are aligned to keep the link straight. The path itself is an A* search that rewards straight runs and penalises grid cells used by other arrows, so parallel arrows separate into their own lanes; straighter arrows route first and claim the direct lanes.