weighted-voronoi
Voronoi treemaps from a spreadsheet, in Python and nothing else.
A treemap that tiles a square with cells whose areas are proportional to your weights, nested down a hierarchy — and where the cells are polygons rather than rectangles. Pure Python on top of numpy, scipy, pandas and matplotlib: no external tools to install, nothing to configure.
pip install weighted-voronoi
Excel input needs one extra (quote it — zsh treats the brackets as a glob):
pip install 'weighted-voronoi[excel]'
Quick start
from weighted_voronoi import treemap
fig = treemap("examples/ecoli_proteome_test.csv", label_level="Level2")
fig.savefig("treemap.png", dpi=200)
treemap() takes a path (CSV, TSV or Excel), a file object, CSV/TSV text, or
a DataFrame, and returns a matplotlib Figure — so it saves to PNG/SVG/PDF,
composes into a larger figure, and displays on its own in a notebook.
A worked example lives in examples/quickstart.ipynb, executed so its figures render without running anything. It covers label levels, styling, DataFrame input, reading the layout back as data, seeds and progress reporting.
Input format
One row per leaf, with the hierarchy spelled out in columns:
| Column | Meaning |
|---|---|
Level1..LevelN |
the groups the leaf belongs to, outermost first (up to 10 levels) |
Name |
the leaf's own name |
Weight |
a positive number; cell areas follow it |
Color |
optional — #1f77b4, rgb()/rgba(), hsl()/hsla(), or a CSS color name |
Rows sharing a level path are grouped, and a group's weight is the sum of its
children. Rows with a non-positive weight are dropped, and a blank level cell
becomes a group named (none).
Colors are per row. Rows that leave the column blank get one derived from their
position in the hierarchy — distinct hues per Level1 group, varied within it —
and a group's own swatch is the weight-averaged color of the tiles beneath it.
Command line
weighted-voronoi examples/ecoli_proteome_test.csv -o map.png --level Level2 --label-color contrast
weighted-voronoi examples/microbe_test.xlsx -o map.svg --borders none --font "Liberation Serif"
weighted-voronoi workbook.xlsx --sheet costs -o map.pdf --random-seed
weighted_voronoi and python -m weighted_voronoi do the same thing. Under
uv, without installing anything:
uvx --from weighted-voronoi weighted-voronoi data.csv -o map.png
Output format follows the -o extension: .png, .svg, .pdf or .eps.
| Flag | Meaning |
|---|---|
--level Level2 / --level Name / --level 2 |
which depth to label |
--label-color white|black|contrast |
contrast picks per label, from the tile's luminance |
--borders white|black|none |
none lets tiles touch |
--font "DejaVu Serif" |
three sans and three serif families, all freely licensed |
--seed N, --random-seed |
reshuffle the layout |
--no-legend, --no-frame |
drop the legend column, drop the frame |
--sheet |
which sheet of an Excel workbook |
--size-inches, --dpi |
output size |
--iterations, --convergence |
trade layout quality against time |
--help lists them all.
A large file takes a few seconds to lay out, so there is a progress bar with an
estimate of the time left. It appears only when stderr is a terminal, and
--no-progress suppresses it. If a map is wanted sooner than exact,
--iterations 20 (from the default 50) roughly halves the time, at the price of
the worst-fitted tiles drifting from 0.05% to 0.3% of the map.
Rendering options
render(), and the corresponding treemap() keywords:
| Argument | Values |
|---|---|
label_level |
None/"Name" for the leaves, a level column name, or a 1-based depth |
label_color |
"white", "black", "contrast" (per label, from the tile's luminance) |
borders |
"white", "black", None (tiles touch) |
font |
a key of FONT_STACKS — three sans, three serif, all freely licensed |
legend, map_inches, legend_inches, dpi, margin, frame |
figure geometry |
Labels are sized to the room their own cell actually has at the anchor, and dropped when they would come out unreadably small. The default font is DejaVu Sans because matplotlib ships it: the figure looks the same on a machine with no fonts installed at all.
There is no interactivity — no hover tooltips, no rebuild button. Pass a
different seed to reshuffle, and re-render to change options. Tile shares are
available programmatically: every record from compute_layout() carries
value and its path.
Using the layout on its own
weighted_voronoi.layout is useful without any of the spreadsheet machinery
above. It needs only numpy and scipy — matplotlib is imported lazily, when
something asks to draw.
import numpy as np
from weighted_voronoi.layout import power_diagram, voronoi_map, voronoi_treemap
square = [[0, 0], [0, 900], [900, 900], [900, 0]]
# A power (Laguerre) diagram: one cell per site, heavier sites take more room
# from their neighbours.
cells = power_diagram(np.array([[300., 450.], [600., 450.]]),
np.array([5000., 1000.]), square)
# Cells whose *areas* follow the weights, found by relaxation.
result = voronoi_map([1.0, 2.0, 4.0, 8.0], square, seed=0)
result["cells"], result["converged"], result["area_error_ratio"]
# The same, recursively, down a hierarchy of dicts.
records = voronoi_treemap({"name": "root", "children": [...]}, square, seed=0)
# Anything slow enough to be worth watching takes a progress callback.
records = voronoi_treemap(tree, square, seed=0,
progress=lambda done, total: print(done, "of", total))
Nothing in there knows about spreadsheets, colors or pandas. Cell areas land within 0.2% of the weights they were asked for, and every loop is bounded — no input hangs the layout, and no seed needs retrying.
Speed is set by the number of nodes: 14 nodes lay out in 0.07 s, 1080 in about 5 s.
In the browser
milo-lab-public.gitlab.io/weighted-voronoi is this package running
under Pyodide: pick a spreadsheet and the layout is
computed by weighted_voronoi.layout in WebAssembly. The file never leaves your
machine — it is read in the browser. Exports are SVG, PNG and PDF.
It is a demonstration, not a replacement: the page downloads ~86 MB of wheels, and WebAssembly runs the layout about 2.6× slower again — a 1000-leaf file takes roughly 12 s. Small maps are comfortable.
The page is index.html, which Pages publishes from the default
branch. It is generated from the package, not written by hand:
python tools/build_web.py
The markup lives beside the builder in tools/template.html. The builder pastes
the package's own modules in verbatim, and tests/test_web_page.py regenerates
and compares, so the committed page cannot fall behind the source.
Tests
pip install 'weighted-voronoi[test]'
python -m pytest tests -q
Without that extra the suite still passes; only the Excel tests skip.
tests/golden/reference.json holds expected layout outputs, captured once and
checked in; test_reference_parity.py checks against them.
tests/golden/regenerate.py says what regenerating it takes, and refuses to run
rather than guessing. Never edit it by hand.
Releasing
Tagging is the whole procedure. CI runs the suite on every push, and on a v*
tag it builds a wheel and an sdist, checks them, and uploads to PyPI via
Trusted Publishing — no token stored here.
# bump `version` in pyproject.toml first -- CI refuses a tag that disagrees
git tag v0.1.1 && git push origin v0.1.1
Licensing
This package's own code is MIT, in LICENSE. layout/ is a derived
work: power.py and clip.py port d3-weighted-voronoi, relax.py ports
d3-voronoi-map, treemap.py ports d3-voronoi-treemap — all BSD
3-Clause, Copyright (c) 2018, LEBEAU Franck — and clip.py is additionally
derived from D3 (BSD 3-Clause, Copyright 2010-2017 Mike Bostock).
No JavaScript is redistributed here, but a port is a derived work, so the notices travel with it: see weighted_voronoi/THIRD-PARTY-LICENSES.md, which ships inside the installed package and in the wheel.
parsing.py and drawing.py are copies of files in the origin project's
python/proteomap/. Nothing enforces that they stay copies —
if you change the parsing rules in one place, change them in all three.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file weighted_voronoi-0.1.0.tar.gz.
File metadata
- Download URL: weighted_voronoi-0.1.0.tar.gz
- Upload date:
- Size: 65.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
uv/0.9.30 {"installer":{"name":"uv","version":"0.9.30","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Debian GNU/Linux","version":"12","id":"bookworm","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
db64dbb0eb3048153ac417d91dc5ae7ee9328bc2e1463d58865f634befa3ceea
|
|
| MD5 |
aa2bd644b3fac4d0e11fe63d9fbc6328
|
|
| BLAKE2b-256 |
c0b458bc3e42f73f66dbcfebc6b257a7c807a420bd2c5ced40dd8cca435ff4ab
|
File details
Details for the file weighted_voronoi-0.1.0-py3-none-any.whl.
File metadata
- Download URL: weighted_voronoi-0.1.0-py3-none-any.whl
- Upload date:
- Size: 49.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
uv/0.9.30 {"installer":{"name":"uv","version":"0.9.30","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Debian GNU/Linux","version":"12","id":"bookworm","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a2189321fadaad2d16cd03cefb5406561cddad2b2a017e2150e9d2f331000f19
|
|
| MD5 |
c9811b91fbbdd4ba82a874ab595c27be
|
|
| BLAKE2b-256 |
f24a54bd519f805d60129c80d98dff21f9e79c8dfdc42534927946110c4cd3fb
|