|
1 | 1 | """ pyplots.ai |
2 | 2 | arc-basic: Basic Arc Diagram |
3 | | -Library: seaborn 0.13.2 | Python 3.13.11 |
4 | | -Quality: 88/100 | Created: 2025-12-23 |
| 3 | +Library: seaborn 0.13.2 | Python 3.14.3 |
| 4 | +Quality: 91/100 | Updated: 2026-02-23 |
5 | 5 | """ |
6 | 6 |
|
7 | | -import matplotlib.patches as patches |
8 | 7 | import matplotlib.pyplot as plt |
9 | 8 | import numpy as np |
10 | 9 | import pandas as pd |
11 | 10 | import seaborn as sns |
12 | | -from matplotlib.lines import Line2D |
13 | 11 |
|
14 | 12 |
|
15 | | -# Data: Character interactions in a story (12 characters for readability) |
16 | | -np.random.seed(42) |
| 13 | +# Data: Character interactions in a story (12 characters) |
17 | 14 | nodes = ["Alice", "Bob", "Carol", "Dave", "Eve", "Frank", "Grace", "Henry", "Ivy", "Jack", "Kate", "Leo"] |
18 | 15 | n_nodes = len(nodes) |
19 | 16 |
|
20 | | -# Create edges with weights (character interaction strength) |
| 17 | +# Edges: (source_index, target_index, interaction_weight) |
21 | 18 | edges = [ |
22 | | - (0, 1, 5), # Alice - Bob (close friends) |
23 | | - (0, 3, 2), # Alice - Dave |
24 | | - (1, 2, 4), # Bob - Carol |
25 | | - (1, 4, 3), # Bob - Eve |
26 | | - (2, 5, 2), # Carol - Frank |
27 | | - (3, 4, 5), # Dave - Eve (close) |
28 | | - (3, 6, 3), # Dave - Grace |
29 | | - (4, 7, 4), # Eve - Henry |
30 | | - (5, 6, 2), # Frank - Grace |
31 | | - (0, 11, 1), # Alice - Leo (distant, long arc) |
32 | | - (2, 6, 3), # Carol - Grace |
33 | | - (1, 5, 2), # Bob - Frank |
34 | | - (7, 8, 4), # Henry - Ivy |
35 | | - (8, 9, 3), # Ivy - Jack |
36 | | - (9, 10, 5), # Jack - Kate (close) |
37 | | - (10, 11, 2), # Kate - Leo |
38 | | - (6, 9, 2), # Grace - Jack |
39 | | - (5, 10, 1), # Frank - Kate (distant) |
| 19 | + (0, 1, 5), # Alice – Bob |
| 20 | + (0, 3, 2), # Alice – Dave |
| 21 | + (1, 2, 4), # Bob – Carol |
| 22 | + (1, 4, 3), # Bob – Eve |
| 23 | + (2, 5, 2), # Carol – Frank |
| 24 | + (3, 4, 5), # Dave – Eve |
| 25 | + (3, 6, 3), # Dave – Grace |
| 26 | + (4, 7, 4), # Eve – Henry |
| 27 | + (5, 6, 2), # Frank – Grace |
| 28 | + (0, 11, 1), # Alice – Leo (long-range) |
| 29 | + (2, 6, 3), # Carol – Grace |
| 30 | + (1, 5, 2), # Bob – Frank |
| 31 | + (7, 8, 4), # Henry – Ivy |
| 32 | + (8, 9, 3), # Ivy – Jack |
| 33 | + (9, 10, 5), # Jack – Kate |
| 34 | + (10, 11, 2), # Kate – Leo |
| 35 | + (6, 9, 2), # Grace – Jack |
| 36 | + (5, 10, 1), # Frank – Kate (long-range) |
40 | 37 | ] |
41 | 38 |
|
42 | | -# Node positions along x-axis |
43 | 39 | x_positions = np.arange(n_nodes) |
44 | 40 |
|
45 | | -# Create figure with seaborn styling - use whitegrid then disable grid |
| 41 | +# Build long-form DataFrame of arc coordinates for seaborn lineplot |
| 42 | +arc_rows = [] |
| 43 | +n_pts = 80 |
| 44 | +for eid, (src, tgt, w) in enumerate(edges): |
| 45 | + x1, x2 = x_positions[src], x_positions[tgt] |
| 46 | + dist = abs(x2 - x1) |
| 47 | + h = dist * 0.4 |
| 48 | + t = np.linspace(0, np.pi, n_pts) |
| 49 | + cx, rx = (x1 + x2) / 2, dist / 2 |
| 50 | + arc_x = cx + rx * np.cos(np.pi - t) |
| 51 | + arc_y = h * np.sin(t) |
| 52 | + for xi, yi in zip(arc_x, arc_y, strict=True): |
| 53 | + arc_rows.append({"x": xi, "y": yi, "weight": w, "edge_id": eid}) |
| 54 | + |
| 55 | +arc_df = pd.DataFrame(arc_rows) |
| 56 | + |
| 57 | +# Categorize weights for seaborn hue encoding |
| 58 | +strength_names = {1: "1 · Weak", 2: "2 · Light", 3: "3 · Moderate", 4: "4 · Strong", 5: "5 · Intense"} |
| 59 | +cat_order = [strength_names[k] for k in sorted(strength_names)] |
| 60 | +arc_df["strength"] = pd.Categorical(arc_df["weight"].map(strength_names), categories=cat_order, ordered=True) |
| 61 | + |
| 62 | +# Theme |
46 | 63 | sns.set_theme(style="white", context="talk", font_scale=1.1) |
47 | 64 | fig, ax = plt.subplots(figsize=(16, 9)) |
48 | 65 |
|
49 | | -# Explicitly disable grid for arc diagram (abstract visualization) |
50 | | -ax.grid(False) |
51 | | - |
52 | | -# Plot nodes as points using seaborn |
53 | | -node_data = pd.DataFrame({"x": x_positions, "y": np.zeros(n_nodes), "node": nodes}) |
54 | | -sns.scatterplot(data=node_data, x="x", y="y", s=600, color="#306998", zorder=5, ax=ax, legend=False) |
55 | | - |
56 | | -# Draw arcs for each edge |
57 | | -for start, end, weight in edges: |
58 | | - x1, x2 = x_positions[start], x_positions[end] |
59 | | - # Arc height proportional to distance between nodes |
60 | | - distance = abs(x2 - x1) |
61 | | - height = distance * 0.4 |
62 | | - |
63 | | - # Arc thickness based on weight |
64 | | - linewidth = weight * 0.8 + 0.5 |
65 | | - |
66 | | - # Create arc using matplotlib patches |
67 | | - center_x = (x1 + x2) / 2 |
68 | | - width = abs(x2 - x1) |
69 | | - |
70 | | - arc = patches.Arc( |
71 | | - (center_x, 0), |
72 | | - width, |
73 | | - height * 2, |
74 | | - angle=0, |
75 | | - theta1=0, |
76 | | - theta2=180, |
77 | | - color="#FFD43B", |
78 | | - linewidth=linewidth, |
79 | | - alpha=0.6, |
80 | | - zorder=2, |
81 | | - ) |
82 | | - ax.add_patch(arc) |
83 | | - |
84 | | -# Add node labels below the axis |
85 | | -for i, name in enumerate(nodes): |
86 | | - ax.text(x_positions[i], -0.15, name, ha="center", va="top", fontsize=16, fontweight="bold", color="#306998") |
87 | | - |
88 | | -# Styling - adjust limits for 12 nodes |
89 | | -ax.set_xlim(-0.8, n_nodes - 0.2) |
90 | | -ax.set_ylim(-0.8, 5.0) # More vertical space for longer arcs |
91 | | -ax.set_title("arc-basic · seaborn · pyplots.ai", fontsize=24, pad=20) |
92 | | - |
93 | | -# Remove all axis elements for abstract visualization |
94 | | -for spine in ax.spines.values(): |
95 | | - spine.set_visible(False) |
96 | | -ax.set_xticks([]) |
97 | | -ax.set_yticks([]) |
98 | | - |
99 | | -# Add a subtle horizontal baseline |
100 | | -ax.axhline(y=0, color="#306998", linewidth=2, alpha=0.3, zorder=1) |
| 66 | +# Viridis palette (reversed so stronger connections = darker/more prominent) |
| 67 | +viridis = sns.color_palette("viridis", as_cmap=True) |
| 68 | +palette = [viridis(v) for v in [0.82, 0.66, 0.48, 0.30, 0.12]] |
| 69 | + |
| 70 | +# Draw arcs via seaborn lineplot (hue=color by strength, size=thickness by weight) |
| 71 | +sns.lineplot( |
| 72 | + data=arc_df, |
| 73 | + x="x", |
| 74 | + y="y", |
| 75 | + hue="strength", |
| 76 | + size="weight", |
| 77 | + units="edge_id", |
| 78 | + estimator=None, |
| 79 | + palette=palette, |
| 80 | + sizes=(2.0, 6.0), |
| 81 | + alpha=0.7, |
| 82 | + ax=ax, |
| 83 | + sort=False, |
| 84 | +) |
101 | 85 |
|
102 | | -# Add legend for arc thickness (interaction strength) |
103 | | -legend_elements = [ |
104 | | - Line2D([0], [0], color="#FFD43B", linewidth=1.3, alpha=0.6, label="Weak (1)"), |
105 | | - Line2D([0], [0], color="#FFD43B", linewidth=2.9, alpha=0.6, label="Medium (3)"), |
106 | | - Line2D([0], [0], color="#FFD43B", linewidth=4.5, alpha=0.6, label="Strong (5)"), |
107 | | -] |
108 | | -legend = ax.legend( |
109 | | - handles=legend_elements, |
| 86 | +# Keep only color legend entries (remove redundant size entries) |
| 87 | +handles, labels = ax.get_legend_handles_labels() |
| 88 | +cat_set = set(cat_order) |
| 89 | +filtered = [(h, lab) for h, lab in zip(handles, labels, strict=True) if lab in cat_set] |
| 90 | +ax.legend( |
| 91 | + [h for h, _ in filtered], |
| 92 | + [lab for _, lab in filtered], |
110 | 93 | title="Interaction Strength", |
| 94 | + title_fontsize=20, |
| 95 | + fontsize=16, |
111 | 96 | loc="upper right", |
112 | | - fontsize=14, |
113 | | - title_fontsize=16, |
114 | 97 | frameon=True, |
115 | 98 | fancybox=True, |
116 | 99 | framealpha=0.9, |
| 100 | + edgecolor="#cccccc", |
| 101 | +) |
| 102 | + |
| 103 | +# Draw nodes with seaborn scatterplot |
| 104 | +node_df = pd.DataFrame({"x": x_positions, "y": np.zeros(n_nodes)}) |
| 105 | +sns.scatterplot( |
| 106 | + data=node_df, x="x", y="y", s=600, color="#306998", zorder=5, ax=ax, legend=False, edgecolor="white", linewidth=1.5 |
| 107 | +) |
| 108 | + |
| 109 | +# Node labels below the baseline |
| 110 | +for i, name in enumerate(nodes): |
| 111 | + ax.text(x_positions[i], -0.22, name, ha="center", va="top", fontsize=16, fontweight="medium", color="#306998") |
| 112 | + |
| 113 | +# Storytelling: highlight the contrast between arc distance and weight |
| 114 | +# The tallest arc (Alice–Leo) is the weakest connection |
| 115 | +ax.annotate( |
| 116 | + "Weakest link, longest reach", |
| 117 | + xy=(5.5, 4.2), |
| 118 | + fontsize=13, |
| 119 | + fontstyle="italic", |
| 120 | + color="#555555", |
| 121 | + ha="center", |
| 122 | + xytext=(2.0, 4.9), |
| 123 | + arrowprops={"arrowstyle": "->", "color": "#888888", "lw": 1.0}, |
| 124 | +) |
| 125 | +# Three strongest bonds are all between nearest neighbors |
| 126 | +ax.annotate( |
| 127 | + "Strongest local bonds", |
| 128 | + xy=(3.5, 0.42), |
| 129 | + fontsize=13, |
| 130 | + fontstyle="italic", |
| 131 | + color="#555555", |
| 132 | + ha="center", |
| 133 | + xytext=(6.0, 2.0), |
| 134 | + arrowprops={"arrowstyle": "->", "color": "#888888", "lw": 1.0}, |
117 | 135 | ) |
118 | 136 |
|
| 137 | +# Axis styling |
| 138 | +ax.set_xlim(-0.8, n_nodes - 0.2) |
| 139 | +ax.set_ylim(-0.45, 5.6) |
| 140 | +ax.set_title("arc-basic \u00b7 seaborn \u00b7 pyplots.ai", fontsize=24, fontweight="medium", pad=20) |
| 141 | +ax.set_xlabel("") |
| 142 | +ax.set_ylabel("") |
| 143 | +sns.despine(ax=ax, left=True, bottom=True) |
| 144 | +ax.set_xticks([]) |
| 145 | +ax.set_yticks([]) |
| 146 | + |
| 147 | +# Subtle horizontal baseline |
| 148 | +ax.axhline(y=0, color="#306998", linewidth=2, alpha=0.3, zorder=1) |
| 149 | + |
119 | 150 | plt.tight_layout() |
120 | 151 | plt.savefig("plot.png", dpi=300, bbox_inches="tight") |
0 commit comments