WAFFLE
绘图 · 第 21 / 36 章
78 字约 1 分钟
基础华夫图(手写网格)

基础华夫图(手写网格)
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 库

用 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 + 图标

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()带百分比标注的华夫图

带百分比标注的华夫图
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()多组华夫图(分面对比)

多组华夫图(分面对比)
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()