codexset-union-intersection-regions.py
set-union-intersection-regions.py
"""Generate set-union-intersection-regions.png in the current directory.
Tested with Python 3.14.4, numpy 2.3.5, matplotlib 3.10.7.
Positions encode membership; circle areas do not encode cardinality.
"""
from pathlib import Path
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
from matplotlib.patches import Circle, Rectangle
import numpy as np
A = {1, 2, 3}
B = {3, 4}
U = set(range(1, 7))
POSITIONS = {1: (-1.2, .35), 2: (-1.2, -.35), 3: (0, 0),
4: (1.5, 0), 5: (-2.1, -1.25), 6: (2.1, -1.25)}
RADIUS = 1.25
BOUNDS = (-2.65, 2.65, -1.65, 1.65)
def draw_panel(ax, region, selected, title, answer):
x = np.linspace(BOUNDS[0], BOUNDS[1], 801)
y = np.linspace(BOUNDS[2], BOUNDS[3], 501)
xx, yy = np.meshgrid(x, y)
in_a = (xx + .8) ** 2 + yy ** 2 <= RADIUS ** 2
in_b = (xx - .8) ** 2 + yy ** 2 <= RADIUS ** 2
ax.contourf(xx, yy, region(in_a, in_b).astype(int),
levels=[-.5, .5, 1.5], colors=["white", "#b9e0d4"])
ax.add_patch(Rectangle((BOUNDS[0], BOUNDS[2]), BOUNDS[1] - BOUNDS[0],
BOUNDS[3] - BOUNDS[2], fill=False, lw=1.3,
edgecolor="#68777e", clip_on=False))
for cx in [-.8, .8]:
ax.add_patch(Circle((cx, 0), RADIUS, fill=False, lw=1.7,
edgecolor="#263c48"))
ax.text(-1.05, 1.03, "$A$", ha="center", fontsize=13)
ax.text(1.05, 1.03, "$B$", ha="center", fontsize=13)
ax.text(-2.45, 1.32, "$U$", fontsize=13)
for n, (px, py) in POSITIONS.items():
assert (((px + .8) ** 2 + py ** 2 <= RADIUS ** 2) == (n in A))
assert (((px - .8) ** 2 + py ** 2 <= RADIUS ** 2) == (n in B))
assert bool(region(n in A, n in B)) == (n in selected)
ax.text(px, py, str(n), ha="center", va="center", fontsize=14,
color="#123e32" if n in selected else "#6a747a",
fontweight="bold" if n in selected else "normal",
bbox={"boxstyle": "circle,pad=.17", "facecolor": "white",
"edgecolor": "#28735d" if n in selected else "#c6cdd1"})
ax.set(xlim=(BOUNDS[0] - .08, BOUNDS[1] + .08),
ylim=(BOUNDS[2] - .08, BOUNDS[3] + .08), aspect="equal")
ax.set_title(title, fontsize=14, pad=12)
ax.text(.5, -.08, answer, transform=ax.transAxes, ha="center", fontsize=12)
ax.axis("off")
def main():
fig, axes = plt.subplots(2, 2, figsize=(8.8, 6.9), dpi=120,
facecolor="white")
panels = [
(lambda a, b: a | b, A | B, r"Union: $A\cup B$", r"$\{1,2,3,4\}$"),
(lambda a, b: a & b, A & B, r"Intersection: $A\cap B$", r"$\{3\}$"),
(lambda a, b: a & ~np.asarray(b), A - B, r"Difference: $A\setminus B$", r"$\{1,2\}$"),
(lambda a, b: b & ~np.asarray(a), B - A, r"Difference: $B\setminus A$", r"$\{4\}$"),
]
for ax, panel in zip(axes.flat, panels):
draw_panel(ax, *panel)
fig.suptitle("One membership picture, four selections", fontsize=16, y=.97)
fig.text(.5, .914, r"$A=\{1,2,3\},\quad B=\{3,4\},\quad U=\{1,2,3,4,5,6\}$",
ha="center", fontsize=12)
fig.text(.5, .025, "Green regions are selected. Numbers show membership.\n"
"Circle areas do not represent the number of elements.",
ha="center", fontsize=10)
fig.subplots_adjust(left=.045, right=.955, top=.84, bottom=.14,
hspace=.48, wspace=.13)
fig.savefig(Path.cwd() / "set-union-intersection-regions.png",
facecolor="white", transparent=False)
plt.close(fig)
if __name__ == "__main__":
main()