$ pip install squarifyTiny & pure Python — the layout core has no dependencies.import squarify★The whole package — four public functions.import matplotlib.pyplot as plt★Only needed forsquarify.plot().import pandas as pdHandy for getting values out of a DataFrame.# layout works without matplotlib installedUse it server-side, then render in JS.
squarify.plot(sizes, ...)★Normalize + lay out + draw in Matplotlib.squarify.normalize_sizes(sizes, dx, dy)★Scale values sosum(sizes) == dx*dy.squarify.squarify(sizes, x, y, dx, dy)★The algorithm → a list of rectangle dicts.squarify.padded_squarify(sizes, x, y, dx, dy)Same, with a small gap so borders show.# that's it — no classes, no stateEvery function is pure: values in, values out.
squarify.plot(sizes=values)★Minimal treemap — it normalizes for you.norm_x=100, norm_y=100The canvas it normalizes into (the defaults).pad=True★Usepadded_squarifyso tiles have gaps.ax=my_axDraw into an existing Axes.ax = squarify.plot(...)Returns the Axes — not a(fig, ax)pair.pad=0.25not a sizepadis a boolean switch; any truthy value gives the same fixed gap.
values.sort(reverse=True)★Descending — squarify never sorts for you.sorted(values, reverse=True)The non-mutating form.# values must be positive★Zero or negative areas make no sense here.df = df.sort_values("v", ascending=False)Sort the frame so labels stay aligned.# unsorted input still "works"silentNo error — you just get a poor, ragged layout.
squarify.normalize_sizes(values, dx, dy)★Rescales so the values sum to the canvas area.norm = normalize_sizes(v, 700, 433)sum(norm) == 700 * 433.# proportions are preservedOnly the scale changes, never the ratios.# plot() calls this internallyOnly needed on the raw-layout path.squarify(raw_values, ...)wrongSkipping normalize gives tiles that overflow the canvas.
rects = squarify.squarify(norm, 0, 0, 700, 433)★Origin(x, y)plus widthdxand heightdy.padded_squarify(norm, 0, 0, 700, 433)Insets each tile so its border is visible.# output order == input order★Sorects[i]pairs withvalues[i]and your labels.x=0, y=0Any origin works — offset for sub-regions.# Bruls, Huizing & van Wijk (2000)Greedy: keep aspect ratios as near 1 as possible.
{"x": 0.0, "y": 0.0, "dx": 327.7, "dy": 433.0}★Origin corner plus width & height.r["x"], r["y"]Lower-left corner in your coordinate system.r["dx"] * r["dy"]The tile's area — proportional to its value.json.dumps(rects)Plain dicts → JSON-serializable for the browser.ax.add_patch(Rectangle((r["x"], r["y"]), r["dx"], r["dy"]))Draw them yourself for full control.
label=["A", "B", "C"]★One label per tile, same order assizes.value=valuesPrint the numbers on the tiles too.label=[f"{n}\n{v}" for n, v in zip(names, values)]★\ngives a two-line name + value label.text_kwargs={"fontsize": 10, "color": "white"}★Passed straight toAxes.text.text_kwargs={"weight": "bold"}Any Matplotlib text property.# labels overflow tiny tilesgotchaNo auto-fit — label only the big ones.
color=["#91DCEA", "#64CDCC", "#5FBB68"]★One colour per tile.color="steelblue"A single colour for everything.color=sns.color_palette("magma", len(values))★Borrow any seaborn / Matplotlib palette.color=[cm.viridis(i/len(v)) for i in range(len(v))]Build a ramp straight from a colormap.alpha=0.8Transparency for the tiles.# default colours are RANDOMgotchaEvery call looks different — passcolor=for stable output.
ec="white"★Edge colour — the cleanest way to separate tiles.bar_kwargs={"edgecolor": "k", "linewidth": 2}★Passed straight toAxes.bar.linewidth=2Loose kwargs merge intobar_kwargs.# explicit kwargs win over bar_kwargsHandy for overriding one property.pad=True, ec="white", linewidth=2Gaps and outlines — the crispest look.
plt.axis("off")★Treemap axes are meaningless — hide them.plt.figure(figsize=(12, 7))Set the size before plotting.plt.title("Market share")A normal Matplotlib title.fig, ax = plt.subplots() ; squarify.plot(..., ax=ax)★Compose treemaps into a bigger figure.plt.savefig("tree.png", dpi=300, bbox_inches="tight")Save it like any other figure.norm_x=200, norm_y=20A wide, short canvas → letterbox tiles.
g = df.groupby("cat")["sales"].sum()★Aggregate to one value per tile.g = g.sort_values(ascending=False)★The mandatory descending sort.squarify.plot(sizes=g.values, label=g.index)★Values and labels stay aligned.df["pct"] = df.v / df.v.sum() * 100Percentages for nicer labels.g = g.head(12)Keep the top-N; tiny tiles are unreadable anyway.
basic: plot(sizes=v) ; plt.axis("off")The two lines you'll write most.labelled: plot(sizes=v, label=names, value=v)Names and numbers on every tile.crisp: plot(sizes=v, pad=True, ec="white", linewidth=2)Clean separation between tiles.palette: plot(sizes=v, color=sns.color_palette("rocket", len(v)), alpha=.9)Dark ramp + white label text.to JSON: json.dumps(squarify(normalize_sizes(v,w,h),0,0,w,h))Hand the layout to d3.js.nested: squarify(sub, r["x"], r["y"], r["dx"], r["dy"])Recurse into one tile to fake a hierarchy.
# flat only — no hierarchylimitOne level of tiles; nest manually by recursing.# no interactivity / tooltipslimitIt's a static Matplotlib drawing.plotly.express.treemap(...)Hierarchical + interactive treemaps.plotly.express.sunburst(...)Radial take on the same nesting.d3.treemap()Full control in the browser.# treemaps beat pies past ~6 slicesArea comparison is easier than angle.