Skip to content
Merged
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
209 changes: 120 additions & 89 deletions plots/arc-basic/implementations/seaborn.py
Original file line number Diff line number Diff line change
@@ -1,120 +1,151 @@
""" pyplots.ai
arc-basic: Basic Arc Diagram
Library: seaborn 0.13.2 | Python 3.13.11
Quality: 88/100 | Created: 2025-12-23
Library: seaborn 0.13.2 | Python 3.14.3
Quality: 91/100 | Updated: 2026-02-23
"""

import matplotlib.patches as patches
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import seaborn as sns
from matplotlib.lines import Line2D


# Data: Character interactions in a story (12 characters for readability)
np.random.seed(42)
# Data: Character interactions in a story (12 characters)
nodes = ["Alice", "Bob", "Carol", "Dave", "Eve", "Frank", "Grace", "Henry", "Ivy", "Jack", "Kate", "Leo"]
n_nodes = len(nodes)

# Create edges with weights (character interaction strength)
# Edges: (source_index, target_index, interaction_weight)
edges = [
(0, 1, 5), # Alice - Bob (close friends)
(0, 3, 2), # Alice - Dave
(1, 2, 4), # Bob - Carol
(1, 4, 3), # Bob - Eve
(2, 5, 2), # Carol - Frank
(3, 4, 5), # Dave - Eve (close)
(3, 6, 3), # Dave - Grace
(4, 7, 4), # Eve - Henry
(5, 6, 2), # Frank - Grace
(0, 11, 1), # Alice - Leo (distant, long arc)
(2, 6, 3), # Carol - Grace
(1, 5, 2), # Bob - Frank
(7, 8, 4), # Henry - Ivy
(8, 9, 3), # Ivy - Jack
(9, 10, 5), # Jack - Kate (close)
(10, 11, 2), # Kate - Leo
(6, 9, 2), # Grace - Jack
(5, 10, 1), # Frank - Kate (distant)
(0, 1, 5), # Alice Bob
(0, 3, 2), # Alice Dave
(1, 2, 4), # Bob Carol
(1, 4, 3), # Bob Eve
(2, 5, 2), # Carol Frank
(3, 4, 5), # Dave Eve
(3, 6, 3), # Dave Grace
(4, 7, 4), # Eve Henry
(5, 6, 2), # Frank Grace
(0, 11, 1), # Alice Leo (long-range)
(2, 6, 3), # Carol Grace
(1, 5, 2), # Bob Frank
(7, 8, 4), # Henry Ivy
(8, 9, 3), # Ivy Jack
(9, 10, 5), # Jack Kate
(10, 11, 2), # Kate Leo
(6, 9, 2), # Grace Jack
(5, 10, 1), # Frank Kate (long-range)
]

# Node positions along x-axis
x_positions = np.arange(n_nodes)

# Create figure with seaborn styling - use whitegrid then disable grid
# Build long-form DataFrame of arc coordinates for seaborn lineplot
arc_rows = []
n_pts = 80
for eid, (src, tgt, w) in enumerate(edges):
x1, x2 = x_positions[src], x_positions[tgt]
dist = abs(x2 - x1)
h = dist * 0.4
t = np.linspace(0, np.pi, n_pts)
cx, rx = (x1 + x2) / 2, dist / 2
arc_x = cx + rx * np.cos(np.pi - t)
arc_y = h * np.sin(t)
for xi, yi in zip(arc_x, arc_y, strict=True):
arc_rows.append({"x": xi, "y": yi, "weight": w, "edge_id": eid})

arc_df = pd.DataFrame(arc_rows)

# Categorize weights for seaborn hue encoding
strength_names = {1: "1 · Weak", 2: "2 · Light", 3: "3 · Moderate", 4: "4 · Strong", 5: "5 · Intense"}
cat_order = [strength_names[k] for k in sorted(strength_names)]
arc_df["strength"] = pd.Categorical(arc_df["weight"].map(strength_names), categories=cat_order, ordered=True)

# Theme
sns.set_theme(style="white", context="talk", font_scale=1.1)
fig, ax = plt.subplots(figsize=(16, 9))

# Explicitly disable grid for arc diagram (abstract visualization)
ax.grid(False)

# Plot nodes as points using seaborn
node_data = pd.DataFrame({"x": x_positions, "y": np.zeros(n_nodes), "node": nodes})
sns.scatterplot(data=node_data, x="x", y="y", s=600, color="#306998", zorder=5, ax=ax, legend=False)

# Draw arcs for each edge
for start, end, weight in edges:
x1, x2 = x_positions[start], x_positions[end]
# Arc height proportional to distance between nodes
distance = abs(x2 - x1)
height = distance * 0.4

# Arc thickness based on weight
linewidth = weight * 0.8 + 0.5

# Create arc using matplotlib patches
center_x = (x1 + x2) / 2
width = abs(x2 - x1)

arc = patches.Arc(
(center_x, 0),
width,
height * 2,
angle=0,
theta1=0,
theta2=180,
color="#FFD43B",
linewidth=linewidth,
alpha=0.6,
zorder=2,
)
ax.add_patch(arc)

# Add node labels below the axis
for i, name in enumerate(nodes):
ax.text(x_positions[i], -0.15, name, ha="center", va="top", fontsize=16, fontweight="bold", color="#306998")

# Styling - adjust limits for 12 nodes
ax.set_xlim(-0.8, n_nodes - 0.2)
ax.set_ylim(-0.8, 5.0) # More vertical space for longer arcs
ax.set_title("arc-basic · seaborn · pyplots.ai", fontsize=24, pad=20)

# Remove all axis elements for abstract visualization
for spine in ax.spines.values():
spine.set_visible(False)
ax.set_xticks([])
ax.set_yticks([])

# Add a subtle horizontal baseline
ax.axhline(y=0, color="#306998", linewidth=2, alpha=0.3, zorder=1)
# Viridis palette (reversed so stronger connections = darker/more prominent)
viridis = sns.color_palette("viridis", as_cmap=True)
palette = [viridis(v) for v in [0.82, 0.66, 0.48, 0.30, 0.12]]

# Draw arcs via seaborn lineplot (hue=color by strength, size=thickness by weight)
sns.lineplot(
data=arc_df,
x="x",
y="y",
hue="strength",
size="weight",
units="edge_id",
estimator=None,
palette=palette,
sizes=(2.0, 6.0),
alpha=0.7,
ax=ax,
sort=False,
)

# Add legend for arc thickness (interaction strength)
legend_elements = [
Line2D([0], [0], color="#FFD43B", linewidth=1.3, alpha=0.6, label="Weak (1)"),
Line2D([0], [0], color="#FFD43B", linewidth=2.9, alpha=0.6, label="Medium (3)"),
Line2D([0], [0], color="#FFD43B", linewidth=4.5, alpha=0.6, label="Strong (5)"),
]
legend = ax.legend(
handles=legend_elements,
# Keep only color legend entries (remove redundant size entries)
handles, labels = ax.get_legend_handles_labels()
cat_set = set(cat_order)
filtered = [(h, lab) for h, lab in zip(handles, labels, strict=True) if lab in cat_set]
ax.legend(
[h for h, _ in filtered],
[lab for _, lab in filtered],
title="Interaction Strength",
title_fontsize=20,
fontsize=16,
loc="upper right",
fontsize=14,
title_fontsize=16,
frameon=True,
fancybox=True,
framealpha=0.9,
edgecolor="#cccccc",
)

# Draw nodes with seaborn scatterplot
node_df = pd.DataFrame({"x": x_positions, "y": np.zeros(n_nodes)})
sns.scatterplot(
data=node_df, x="x", y="y", s=600, color="#306998", zorder=5, ax=ax, legend=False, edgecolor="white", linewidth=1.5
)

# Node labels below the baseline
for i, name in enumerate(nodes):
ax.text(x_positions[i], -0.22, name, ha="center", va="top", fontsize=16, fontweight="medium", color="#306998")

# Storytelling: highlight the contrast between arc distance and weight
# The tallest arc (Alice–Leo) is the weakest connection
ax.annotate(
"Weakest link, longest reach",
xy=(5.5, 4.2),
fontsize=13,
fontstyle="italic",
color="#555555",
ha="center",
xytext=(2.0, 4.9),
arrowprops={"arrowstyle": "->", "color": "#888888", "lw": 1.0},
)
# Three strongest bonds are all between nearest neighbors
ax.annotate(
"Strongest local bonds",
xy=(3.5, 0.42),
fontsize=13,
fontstyle="italic",
color="#555555",
ha="center",
xytext=(6.0, 2.0),
arrowprops={"arrowstyle": "->", "color": "#888888", "lw": 1.0},
)

# Axis styling
ax.set_xlim(-0.8, n_nodes - 0.2)
ax.set_ylim(-0.45, 5.6)
ax.set_title("arc-basic \u00b7 seaborn \u00b7 pyplots.ai", fontsize=24, fontweight="medium", pad=20)
ax.set_xlabel("")
ax.set_ylabel("")
sns.despine(ax=ax, left=True, bottom=True)
ax.set_xticks([])
ax.set_yticks([])

# Subtle horizontal baseline
ax.axhline(y=0, color="#306998", linewidth=2, alpha=0.3, zorder=1)

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