$ pip install pywafflePython 3.5+ and Matplotlib; Font Awesome ships bundled.from pywaffle import Waffle★Imports the Figure class — that's the whole package.import matplotlib.pyplot as plt★You always go throughplt.figure().import pandas as pdA Series works directly asvalues.# MIT licensed, by Guangyang LiDocs: pywaffle.readthedocs.io
fig = plt.figure(FigureClass=Waffle, ...)★A Figure class, not a plotting function.# FigureClass & figsize → pyplotEvery other keyword belongs toWaffle.Waffle.make_waffle(ax=ax, rows=5, values=[48,46,6])★Draw into an Axes you already have.Waffle.fig_argsEvery Waffle argument with its default.pywaffle.waffle(...)no such thingThere is no plotting function — only the class.
values=[30, 16, 4]★A plain list — one entry per category.values={"Cat1": 30, "Cat2": 16}★A dict — keys become the legend labels automatically.values=df["sales"]A pandas Series works too.labels=["Car (58%)", "Truck (11%)"]★Explicit names; a dict's keys override this.# DataFrame index is NOT used as labelsgotchaUnlike a dict — passlabels=yourself.
rows=5★At least one ofrows/columnsis required.rows=5★Only one given → the other is computed; values used as absolute block counts.rows=5, columns=10★Both given → grid fixed at 50 blocks; values are scaled to fit.rows=10, columns=10100 blocks → each block reads as exactly 1%.# both set = your numbers get rescaledgotchaGive only one if you want exact counts.
rounding_rule="nearest"The default — round-half-to-even.rounding_rule="floor"★The docs' pick: the most consistent rule.rounding_rule="ceil"Rounds up.# [48, 46, 6] on 5x10 → 24, 23, 3Scaled to the 50-block grid.# nearest / ceil can overflow the gridgotchaThe last category's blocks may be cut off — usefloor.
colors=["#c1d82f", "#00a4e4", "#fbb034"]★One colour per category; length must matchvalues.cmap_name="Set2"★The default palette whencolorsis omitted.cmap_name="tab10" · "Dark2" · "Paired"Also Pastel1/2, Accent, Set1/3, tab20, tab20b, tab20c.cmap_name="viridis"won't workOnly qualitative colormaps are supported — sequential ones don't work.fig.set_facecolor("#DDDDDD")Background of the whole figure.
legend={"loc": "upper left", "bbox_to_anchor": (1, 1)}★Anyplt.legendparameter, in a dict.legend={"ncol": 4, "framealpha": 0}Horizontal, frameless legend under the chart.icon_legend=True★Show the icon in the legend instead of a colour bar.# dict values → labels for freeNolabels=needed.legend_loc="upper left"wronglegendtakes a dict, not loose keywords.
title={"label": "Vehicle Sales", "loc": "left"}★Anyset_titleparameter, in a dict.title={"label": "...", "size": 15}Font size and alignment inline.title={"fontdict": {"fontsize": 20}}Full font control.fig.text(0.5, 0.02, "source: ...")It's a Matplotlib Figure — annotate freely.
block_aspect_ratio=1★Width ÷ height. Default 1 → squares.block_aspect_ratio=1.618Wider than tall.interval_ratio_x=0.2★Horizontal gap as a ratio of block width.interval_ratio_y=0.2Vertical gap as a ratio of block height.# all three ignored when using iconsgotchaIcons size themselves — usefont_size.
starting_location="SW"★Default — lower-left. Also NW, NE, SE.vertical=False★Default — fills column by column.vertical=TrueFills row by row instead.block_arranging_style="normal"★Default — a new category starts where the last ended.block_arranging_style="snake"Alternating direction each line.block_arranging_style="new-line"Each category starts on a fresh line.# new-line needs only ONE of rows/columnslimitPlusverticalmatching the axis you set.
characters="⬤"★Any Unicode character instead of a rectangle.characters=["●", "■", "▲"]One per category — length must matchvalues.icons="star"★A Font Awesome name — same icon for every category.icons=["car-side", "truck", "motorcycle"]★Search names at fontawesome.com/search.icon_style="solid"Default; also"regular"and"brands".font_size=12★Size of characters/icons; default follows block size.font_file="/path/to/font.ttf"A custom.ttf/.otffor characters.icon_size=12deprecatedUsefont_sizeinstead.
plots={311: {...}, 312: {...}, 313: {...}}★Three stacked subplots, each its own waffle.plots={(3,1,1): {...}}Tuple form: rows, columns, index.# subplots inherit parent parameters★Setcolors/rowsonce at the top level.plots={311: {"values": v1, "title": {"label": "2023"}}}Override per subplot as needed.# 3-digit form needs all digits < 10noteUse the tuple form beyond 9.
figsize=(8, 4)★A pyplot parameter, passed in the same call.plot_anchor="W"Default alignment; also C, N, S, E, NW, NE, SW, SE.tight=TrueDefault — callstight_layoutwhen drawing.tight=Falsefixes warningSilences the harmless "Axes not compatible with tight_layout" warning.fig.savefig("waffle.png", bbox_inches="tight")★Save it like any figure.plt.show()Display interactively.
basic: FigureClass=Waffle, rows=5, values=[30,16,4]The smallest useful call.percent: rows=10, columns=10, values=data100 blocks → one block per percent.pictogram: icons="child", font_size=18, icon_legend=TrueIcons instead of squares.labels with %: labels=[f"{k} ({v}%)" for k,v in data.items()]Percentages in the legend.waffle bar: rows=1, block_arranging_style="new-line"A single-row stacked bar of blocks.grid: plots={221:{..}, 222:{..}, 223:{..}, 224:{..}}Small multiples of waffles.