Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 53 additions & 42 deletions plots/waffle-basic/implementations/python/matplotlib.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
""" anyplot.ai
waffle-basic: Basic Waffle Chart
Library: matplotlib 3.10.9 | Python 3.13.13
Quality: 91/100 | Updated: 2026-05-05
Library: matplotlib 3.11.1 | Python 3.13.14
Quality: 89/100 | Updated: 2026-07-26
"""

import os

import matplotlib.patches as mpatches
import matplotlib.patheffects as patheffects
import matplotlib.pyplot as plt
import numpy as np

Expand All @@ -18,78 +19,88 @@
INK = "#1A1A17" if THEME == "light" else "#F0EFE8"
INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0"

# Okabe-Ito palette (canonical order)
# Imprint palette (canonical order)
IMPRINT = ["#009E73", "#C475FD", "#4467A3", "#BD8233"]

# Data - Survey responses (sums to 100%)
categories = ["Strongly Agree", "Agree", "Neutral", "Disagree"]
values = [48, 32, 15, 5]
dominant = int(np.argmax(values))

# Create 10x10 grid (100 squares, each = 1%)
# Build a 10x10 grid (100 squares, each = 1%), filled category by category
grid_size = 10
total_squares = grid_size * grid_size

# Build grid data - fill squares by category
grid = np.zeros(total_squares, dtype=int)
start_idx = 0
grid = np.zeros(grid_size * grid_size, dtype=int)
start = 0
for i, val in enumerate(values):
grid[start_idx : start_idx + val] = i
start_idx += val

# Reshape to 10x10 grid
grid[start : start + val] = i
start += val
grid = grid.reshape((grid_size, grid_size))

# Create plot (4800x2700 px)
fig, ax = plt.subplots(figsize=(16, 9), facecolor=PAGE_BG)
# Square canvas (2400x2400) - waffle charts are grid-based/symmetric, no preferred axis
fig, ax = plt.subplots(figsize=(6, 6), dpi=400, facecolor=PAGE_BG)
ax.set_facecolor(PAGE_BG)
# Pin the axes to a literal square in figure-inch space so set_aspect("equal")
# never has to auto-shrink the box, keeping title/subtitle/legend placement predictable.
ax.set_position([0.13, 0.14, 0.74, 0.74])

# Draw squares
square_size = 0.9 # Slightly smaller than 1 for gaps
# Draw squares with a soft drop shadow for depth; the dominant category gets an
# ink-toned outline instead of a background-blended one, a quiet emphasis cue
# that draws the eye to the largest segment without adding overlapping text.
square_size = 0.86
gap = (1 - square_size) / 2
for row in range(grid_size):
for col in range(grid_size):
category_idx = grid[row, col]
cat = int(grid[row, col])
emphasize = cat == dominant
rect = mpatches.FancyBboxPatch(
(col + 0.05, row + 0.05),
(col + gap, row + gap),
square_size,
square_size,
boxstyle="round,pad=0.02,rounding_size=0.1",
facecolor=IMPRINT[category_idx],
edgecolor=PAGE_BG,
linewidth=1.5,
boxstyle="round,pad=0.015,rounding_size=0.09",
facecolor=IMPRINT[cat],
edgecolor=INK_SOFT if emphasize else PAGE_BG,
linewidth=2.2 if emphasize else 1.3,
)
rect.set_path_effects(
[patheffects.withSimplePatchShadow(offset=(1.0, -1.0), shadow_rgbFace=INK, alpha=0.2), patheffects.Normal()]
)
ax.add_patch(rect)

# Set axis limits and remove axes
ax.set_xlim(0, grid_size)
ax.set_ylim(0, grid_size)
ax.set_aspect("equal")
ax.axis("off")

# Create legend with percentage labels
# Legend with percentage labels, centered under the grid
legend_patches = [
mpatches.Patch(color=IMPRINT[i], label=f"{categories[i]} ({values[i]}%)") for i in range(len(categories))
mpatches.Patch(facecolor=IMPRINT[i], label=f"{categories[i]} ({values[i]}%)") for i in range(len(categories))
]
leg = ax.legend(
handles=legend_patches, loc="center left", bbox_to_anchor=(1.02, 0.5), fontsize=18, frameon=True, fancybox=True
leg = fig.legend(
handles=legend_patches,
loc="center",
bbox_to_anchor=(0.5, 0.06),
ncol=2,
fontsize=8,
frameon=True,
fancybox=True,
columnspacing=1.4,
handletextpad=0.6,
)
if leg:
leg.get_frame().set_facecolor(ELEVATED_BG)
leg.get_frame().set_edgecolor(INK_SOFT)
leg.get_frame().set_linewidth(0.8)
plt.setp(leg.get_texts(), color=INK_SOFT)
leg.get_frame().set_facecolor(ELEVATED_BG)
leg.get_frame().set_edgecolor(INK_SOFT)
leg.get_frame().set_linewidth(0.8)
plt.setp(leg.get_texts(), color=INK_SOFT)

# Title and subtitle
ax.text(
# Title and subtitle (figure-level text, independent of the aspect-locked axes box)
fig.text(
0.5,
1.08,
"waffle-basic · matplotlib · anyplot.ai",
transform=ax.transAxes,
fontsize=24,
0.95,
"waffle-basic · python · matplotlib · anyplot.ai",
fontsize=12,
fontweight="medium",
color=INK,
ha="center",
)
ax.text(0.5, 1.02, "Survey Response Distribution", transform=ax.transAxes, fontsize=16, color=INK_SOFT, ha="center")
fig.text(0.5, 0.915, "Survey Response Distribution", fontsize=10, color=INK_SOFT, ha="center")

plt.tight_layout()
plt.savefig(f"plot-{THEME}.png", dpi=300, bbox_inches="tight", facecolor=PAGE_BG)
plt.savefig(f"plot-{THEME}.png", dpi=400, facecolor=PAGE_BG)
Loading