目录5

文章绘图第 21 章

WAFFLE

绘图 · 第 21 / 36 章

基础华夫图(手写网格)

chart_082

基础华夫图(手写网格)
Python64 行
import matplotlib.pyplot as plt
import numpy as np

COLORS = ["#A8552E", "#35666E", "#D0A64F", "#3F2E18", "#7E8F52"]
BG = "#F7F3EC"

# 数据:各分类占比(合计 100)
categories = ["Alpha", "Beta", "Gamma", "Delta"]
values = [40, 30, 20, 10]
n_total = 100   # 总格子数

# 计算每类占用的格子数
counts = [int(round(v / 100 * n_total)) for v in values]
# 修正舍入误差
counts[-1] += n_total - sum(counts)

# 生成 10×10 网格,按顺序填充类别
grid = np.zeros((10, 10), dtype=int)
idx = 0
for i in range(10):
    for j in range(10):
        # 从下往上填
        row = 9 - i
        col = j
        # 累加判断该格子属于哪一类
        cum = 0
        for k, c in enumerate(counts):
            if idx < cum + c:
                grid[row, col] = k
                break
            cum += c
        idx += 1

fig, ax = plt.subplots(figsize=(7, 7))

# 构造颜色映射
from matplotlib.colors import ListedColormap
cmap = ListedColormap(COLORS[:len(categories)])

ax.imshow(grid, cmap=cmap, vmin=0, vmax=len(categories) - 1)

# 画网格线
for k in np.arange(-0.5, 10, 1):
    ax.axhline(k, color="white", linewidth=1.5)
    ax.axvline(k, color="white", linewidth=1.5)

ax.set_xticks([])
ax.set_yticks([])

for s in ("top", "right", "left", "bottom"):
    ax.spines[s].set_visible(False)

ax.set_title("Waffle Chart", fontsize=15, color=COLORS[3], pad=12)

# 图例
from matplotlib.patches import Patch
handles = [Patch(facecolor=c, edgecolor="white", label=f"{name} ({v}%)")
           for name, v, c in zip(categories, values, COLORS)]
ax.legend(handles=handles, frameon=False, fontsize=11,
          labelcolor=COLORS[3], loc="center left",
          bbox_to_anchor=(1.02, 0.5))

plt.tight_layout()
plt.show()

用 pywaffle 库

chart_083

用 pywaffle 库
Python33 行
import matplotlib.pyplot as plt
from pywaffle import Waffle

COLORS = ["#A8552E", "#35666E", "#D0A64F", "#3F2E18", "#7E8F52"]
BG = "#F7F3EC"

data = {
    "Alpha": 40,
    "Beta": 30,
    "Gamma": 20,
    "Delta": 10,
}

fig = plt.figure(
    FigureClass=Waffle,
    rows=10,
    columns=10,
    values=data,
    colors=COLORS[:len(data)],
    title={"label": "Waffle Chart", "loc": "center",
           "fontsize": 15, "color": COLORS[3]},
    legend={"loc": "center left",
            "bbox_to_anchor": (1.02, 0.5),
            "frameon": False,
            "fontsize": 11,
            "labelcolor": COLORS[3]},
    figsize=(9, 7),
    starting_location="SW",   # 从左下角开始填充
    block_aspect_ratio=1.0,   # 方块正方形
)

plt.tight_layout()
plt.show()

pywaffle + 图标

chart_084

pywaffle + 图标
Python28 行
import matplotlib.pyplot as plt
from pywaffle import Waffle

COLORS = ["#A8552E", "#35666E", "#D0A64F", "#3F2E18", "#7E8F52"]
BG = "#F7F3EC"

data = {"Alpha": 40, "Beta": 30, "Gamma": 20, "Delta": 10}

fig = plt.figure(
    FigureClass=Waffle,
    rows=5,
    columns=10,
    values=data,
    colors=COLORS[:len(data)],
    characters="●",          # 用实心圆代替方块
    title={"label": "Waffle with Icons", "loc": "center",
           "fontsize": 15, "color": COLORS[3]},
    legend={"loc": "center left",
            "bbox_to_anchor": (1.02, 0.5),
            "frameon": False,
            "fontsize": 11,
            "labelcolor": COLORS[3]},
    figsize=(9, 5),
    font_size=24,            # 图标字号
)

plt.tight_layout()
plt.show()

带百分比标注的华夫图

chart_085

带百分比标注的华夫图
Python69 行
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.patches import Patch

COLORS = ["#A8552E", "#35666E", "#D0A64F", "#3F2E18", "#7E8F52"]
BG = "#F7F3EC"

categories = ["Alpha", "Beta", "Gamma", "Delta"]
values = [42, 28, 18, 12]
n_total = 100

counts = [int(round(v / 100 * n_total)) for v in values]
counts[-1] += n_total - sum(counts)

# 生成网格(从左上往右下填)
grid = np.zeros((10, 10), dtype=int)
idx = 0
for i in range(10):
    for j in range(10):
        cum = 0
        for k, c in enumerate(counts):
            if idx < cum + c:
                grid[i, j] = k
                break
            cum += c
        idx += 1

fig, ax = plt.subplots(figsize=(7.5, 7))

from matplotlib.colors import ListedColormap
cmap = ListedColormap(COLORS[:len(categories)])
ax.imshow(grid, cmap=cmap, vmin=0, vmax=len(categories) - 1)

# 网格白线
for k in np.arange(-0.5, 10, 1):
    ax.axhline(k, color="white", linewidth=1.8)
    ax.axvline(k, color="white", linewidth=1.8)

# 在每类中心标百分比
idx = 0
for k, (name, v, c) in enumerate(zip(categories, values, COLORS)):
    # 该类格子的中心
    start = idx
    end = idx + counts[k]
    center = (start + end) / 2
    row = int(center // 10)
    col = int(center % 10)
    ax.text(col, row, f"{v}%",
            ha="center", va="center",
            fontsize=12, color="white",
            fontweight="bold", zorder=5)
    idx = end

ax.set_xticks([])
ax.set_yticks([])
for s in ("top", "right", "left", "bottom"):
    ax.spines[s].set_visible(False)

ax.set_title("Waffle Chart with Percentage Labels",
             fontsize=15, color=COLORS[3], pad=12)

handles = [Patch(facecolor=c, edgecolor="white", label=name)
           for name, c in zip(categories, COLORS)]
ax.legend(handles=handles, frameon=False, fontsize=11,
          labelcolor=COLORS[3], loc="center left",
          bbox_to_anchor=(1.02, 0.5))

plt.tight_layout()
plt.show()

多组华夫图(分面对比)

chart_086

多组华夫图(分面对比)
Python61 行
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.colors import ListedColormap
from matplotlib.patches import Patch

COLORS = ["#A8552E", "#35666E", "#D0A64F", "#3F2E18", "#7E8F52"]
BG = "#F7F3EC"

categories = ["Alpha", "Beta", "Gamma", "Delta"]
snapshots = {
    "2023": [35, 30, 22, 13],
    "2024": [40, 28, 20, 12],
    "2025": [48, 25, 18, 9],
}

def make_grid(values, n_total=100, n_side=10):
    counts = [int(round(v / 100 * n_total)) for v in values]
    counts[-1] += n_total - sum(counts)
    grid = np.zeros((n_side, n_side), dtype=int)
    idx = 0
    for i in range(n_side):
        for j in range(n_side):
            cum = 0
            for k, c in enumerate(counts):
                if idx < cum + c:
                    grid[i, j] = k
                    break
                cum += c
            idx += 1
    return grid

cmap = ListedColormap(COLORS[:len(categories)])

fig, axes = plt.subplots(1, 3, figsize=(15, 5.5))

for ax, (year, values) in zip(axes, snapshots.items()):
    grid = make_grid(values)
    ax.imshow(grid, cmap=cmap, vmin=0, vmax=len(categories) - 1)

    for k in np.arange(-0.5, 10, 1):
        ax.axhline(k, color="white", linewidth=1.2)
        ax.axvline(k, color="white", linewidth=1.2)

    ax.set_xticks([])
    ax.set_yticks([])
    for s in ("top", "right", "left", "bottom"):
        ax.spines[s].set_visible(False)
    ax.set_title(year, fontsize=13, color=COLORS[3], pad=8)

# 共享图例
handles = [Patch(facecolor=c, edgecolor="white", label=name)
           for name, c in zip(categories, COLORS)]
fig.legend(handles=handles, frameon=False, fontsize=11,
           labelcolor=COLORS[3], loc="lower center",
           ncol=4, bbox_to_anchor=(0.5, -0.02))

fig.suptitle("Waffle Charts — Distribution over Time",
             fontsize=15, color=COLORS[3], y=1.00)

plt.tight_layout()
plt.show()