Skip to content

Commit 1998bd9

Browse files
update(arc-basic): matplotlib — comprehensive quality review (#4367)
## Summary Updated **matplotlib** implementation for **arc-basic**. **Changes:** Comprehensive quality review and update ### Changes - Updated implementation with improved code quality and visual design - Updated spec tags (added categorical, ordinal; replaced connection-visualization with 2d) - Updated spec with Example line in Data section ## Test Plan - [x] Preview images uploaded to GCS staging - [x] Implementation file passes ruff format/check - [x] Metadata YAML updated with current versions - [ ] Automated review triggered --- Generated with [Claude Code](https://claude.com/claude-code) `/update` command --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
1 parent b1e8c8c commit 1998bd9

4 files changed

Lines changed: 260 additions & 169 deletions

File tree

Lines changed: 115 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,23 @@
11
""" pyplots.ai
22
arc-basic: Basic Arc Diagram
3-
Library: matplotlib 3.10.8 | Python 3.13.11
4-
Quality: 91/100 | Created: 2025-12-23
3+
Library: matplotlib 3.10.8 | Python 3.14.3
4+
Quality: 91/100 | Updated: 2026-02-23
55
"""
66

7+
import matplotlib.colors as mcolors
78
import matplotlib.patches as mpatches
9+
import matplotlib.path as mpath
810
import matplotlib.pyplot as plt
911
import numpy as np
1012

1113

1214
# Data: Character interactions in a story chapter
13-
np.random.seed(42)
14-
1515
nodes = ["Alice", "Bob", "Carol", "David", "Eve", "Frank", "Grace", "Henry", "Iris", "Jack"]
1616
n_nodes = len(nodes)
1717

18-
# Edges: pairs of connected nodes with weights (start, end, weight)
18+
# Edges: (source_index, target_index, weight)
1919
edges = [
20-
(0, 1, 3), # Alice-Bob (strong connection)
20+
(0, 1, 3), # Alice-Bob (strong)
2121
(0, 3, 2), # Alice-David
2222
(1, 2, 2), # Bob-Carol
2323
(2, 4, 1), # Carol-Eve
@@ -34,62 +34,126 @@
3434
(8, 9, 2), # Iris-Jack
3535
]
3636

37-
# Create plot
37+
weights = [w for _, _, w in edges]
38+
weight_min, weight_max = min(weights), max(weights)
39+
40+
# Weighted node degrees for size variation (data storytelling)
41+
node_degrees = [0] * n_nodes
42+
for s, e, w in edges:
43+
node_degrees[s] += w
44+
node_degrees[e] += w
45+
46+
# Truncated Blues colormap — avoids near-white so weight-1 arcs stay clearly visible
47+
blues_raw = plt.cm.Blues(np.linspace(0.35, 0.95, 256))
48+
arc_cmap = mcolors.LinearSegmentedColormap.from_list("TruncBlues", blues_raw)
49+
norm = mcolors.Normalize(vmin=weight_min, vmax=weight_max)
50+
51+
# Plot
3852
fig, ax = plt.subplots(figsize=(16, 9))
3953

40-
# Node positions along x-axis
41-
x_positions = np.linspace(0.05, 0.95, n_nodes)
42-
y_baseline = 0.15
54+
x_positions = np.linspace(0.06, 0.90, n_nodes)
55+
y_baseline = 0.08
4356

44-
# Draw arcs
45-
for start, end, weight in edges:
57+
# Arcs via PathPatch with cubic Bézier curves (distinctive matplotlib feature)
58+
for start, end, weight in sorted(edges, key=lambda e: e[2]):
4659
x_start = x_positions[start]
4760
x_end = x_positions[end]
48-
49-
# Arc height proportional to distance between nodes
5061
distance = abs(end - start)
51-
height = 0.07 * distance
52-
53-
# Center and width of the arc
54-
x_center = (x_start + x_end) / 2
55-
arc_width = abs(x_end - x_start)
56-
57-
# Arc thickness based on weight
58-
linewidth = 2.0 + weight * 1.2
59-
60-
# Semi-transparent arcs
61-
alpha = 0.55
62-
63-
# Create arc using Arc patch
64-
arc = mpatches.Arc(
65-
(x_center, y_baseline),
66-
width=arc_width,
67-
height=height * 2,
68-
angle=0,
69-
theta1=0,
70-
theta2=180,
71-
color="#306998",
72-
linewidth=linewidth,
73-
alpha=alpha,
62+
peak = 0.065 * distance
63+
64+
path = mpath.Path(
65+
[
66+
(x_start, y_baseline),
67+
(x_start, y_baseline + peak * 1.35),
68+
(x_end, y_baseline + peak * 1.35),
69+
(x_end, y_baseline),
70+
],
71+
[mpath.Path.MOVETO, mpath.Path.CURVE4, mpath.Path.CURVE4, mpath.Path.CURVE4],
7472
)
75-
ax.add_patch(arc)
7673

77-
# Draw nodes
78-
ax.scatter(x_positions, [y_baseline] * n_nodes, s=500, c="#FFD43B", edgecolors="#306998", linewidths=2.5, zorder=5)
79-
80-
# Add node labels
81-
for x, name in zip(x_positions, nodes, strict=True):
82-
ax.text(x, y_baseline - 0.06, name, ha="center", va="top", fontsize=16, fontweight="bold", color="#306998")
83-
84-
# Styling
85-
ax.set_xlim(-0.02, 1.02)
86-
ax.set_ylim(-0.05, 0.85)
87-
ax.set_aspect("equal")
74+
patch = mpatches.PathPatch(
75+
path,
76+
facecolor="none",
77+
edgecolor=arc_cmap(norm(weight)),
78+
linewidth=1.5 + weight * 1.8,
79+
alpha=0.8,
80+
capstyle="round",
81+
)
82+
ax.add_patch(patch)
83+
84+
# Node sizes proportional to weighted degree (reveals hub characters)
85+
max_degree = max(node_degrees)
86+
node_sizes = [300 + 350 * (d / max_degree) for d in node_degrees]
87+
88+
# Highlight protagonist Alice with distinct accent color
89+
node_colors = ["#FF6B35" if i == 0 else "#FFD43B" for i in range(n_nodes)]
90+
node_edge_colors = ["#B8441A" if i == 0 else "#306998" for i in range(n_nodes)]
91+
92+
ax.scatter(
93+
x_positions,
94+
[y_baseline] * n_nodes,
95+
s=node_sizes,
96+
c=node_colors,
97+
edgecolors=node_edge_colors,
98+
linewidths=2.5,
99+
zorder=5,
100+
)
101+
102+
# Node labels with typographic hierarchy
103+
for i, (x, name) in enumerate(zip(x_positions, nodes, strict=True)):
104+
ax.text(
105+
x,
106+
y_baseline - 0.04,
107+
name,
108+
ha="center",
109+
va="top",
110+
fontsize=16,
111+
fontweight="heavy" if i == 0 else "bold",
112+
color="#306998",
113+
)
88114

89-
# Remove axes
115+
# Colorbar for connection strength
116+
sm = plt.cm.ScalarMappable(cmap=arc_cmap, norm=norm)
117+
sm.set_array([])
118+
cbar = fig.colorbar(sm, ax=ax, shrink=0.4, aspect=15, pad=0.02)
119+
cbar.set_label("Connection Strength", fontsize=16)
120+
cbar.set_ticks([1, 2, 3])
121+
cbar.ax.tick_params(labelsize=16)
122+
123+
# Subtle baseline
124+
ax.plot(
125+
[x_positions[0] - 0.02, x_positions[-1] + 0.02],
126+
[y_baseline, y_baseline],
127+
color="#306998",
128+
linewidth=0.8,
129+
alpha=0.2,
130+
zorder=1,
131+
)
132+
133+
# Layout
134+
ax.set_xlim(-0.02, 0.98)
135+
ax.set_ylim(-0.06, 0.68)
90136
ax.axis("off")
91137

92-
ax.set_title("Character Interactions · arc-basic · matplotlib · pyplots.ai", fontsize=24, pad=20)
138+
# Title with narrative subtitle
139+
ax.set_title(
140+
"Character Interactions \u00b7 arc-basic \u00b7 matplotlib \u00b7 pyplots.ai",
141+
fontsize=24,
142+
fontweight="medium",
143+
pad=36,
144+
)
145+
146+
ax.text(
147+
0.5,
148+
1.01,
149+
"Node size reflects connection activity \u00b7 Alice (orange) is the central character",
150+
ha="center",
151+
va="bottom",
152+
fontsize=13,
153+
color="#888888",
154+
fontstyle="italic",
155+
transform=ax.transAxes,
156+
)
93157

94158
plt.tight_layout()
95159
plt.savefig("plot.png", dpi=300, bbox_inches="tight")

0 commit comments

Comments
 (0)