Skip to content

Commit 163e31d

Browse files
update(arc-basic): letsplot — comprehensive quality review (#4375)
## Summary Updated **letsplot** implementation for **arc-basic**. **Changes:** Comprehensive quality review and update ### Changes - Updated implementation with improved code quality and visual design ## 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> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
1 parent 6f4a89c commit 163e31d

2 files changed

Lines changed: 265 additions & 158 deletions

File tree

Lines changed: 125 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
""" pyplots.ai
22
arc-basic: Basic Arc Diagram
3-
Library: letsplot 4.8.2 | Python 3.13.11
4-
Quality: 92/100 | Created: 2025-12-23
3+
Library: letsplot 4.8.2 | Python 3.14.3
4+
Quality: 89/100 | Updated: 2026-02-23
55
"""
66

77
import numpy as np
@@ -10,13 +10,18 @@
1010
LetsPlot,
1111
aes,
1212
element_blank,
13+
element_rect,
1314
element_text,
1415
geom_path,
1516
geom_point,
17+
geom_segment,
1618
geom_text,
1719
ggplot,
1820
ggsize,
1921
labs,
22+
layer_tooltips,
23+
scale_alpha_identity,
24+
scale_color_identity,
2025
scale_size_identity,
2126
theme,
2227
xlim,
@@ -28,14 +33,12 @@
2833
LetsPlot.setup_html()
2934

3035
# Data: Character interactions in a story chapter
31-
np.random.seed(42)
32-
3336
nodes = ["Alice", "Bob", "Carol", "David", "Eve", "Frank", "Grace", "Henry", "Iris", "Jack"]
3437
n_nodes = len(nodes)
3538

3639
# Edges: pairs of connected nodes with weights (source, target, weight)
3740
edges = [
38-
(0, 1, 3), # Alice-Bob (strong connection)
41+
(0, 1, 3), # Alice-Bob (strong)
3942
(0, 3, 2), # Alice-David
4043
(1, 2, 2), # Bob-Carol
4144
(2, 4, 1), # Carol-Eve
@@ -52,9 +55,20 @@
5255
(8, 9, 2), # Iris-Jack
5356
]
5457

55-
# Node positions along x-axis
56-
x_positions = np.linspace(0, 1, n_nodes)
57-
y_baseline = 0.1
58+
# Node positions along x-axis — wider spacing for label readability
59+
x_positions = np.linspace(0, 1.3, n_nodes)
60+
y_baseline = 0.06
61+
62+
# Count connections per node for visual hierarchy
63+
connections = [0] * n_nodes
64+
for s, t, w in edges:
65+
connections[s] += w
66+
connections[t] += w
67+
68+
# Arc color intensity by weight
69+
weight_colors = {1: "#6A9BB5", 2: "#306998", 3: "#1A3A5C"}
70+
weight_alphas = {1: 0.7, 2: 0.75, 3: 0.9}
71+
weight_labels = {1: "Weak", 2: "Moderate", 3: "Strong"}
5872

5973
# Create arc data for geom_path
6074
arc_data = []
@@ -66,57 +80,138 @@
6680
distance = abs(end - start)
6781
height = 0.08 * distance
6882

69-
# Generate points along the arc (semi-circle)
83+
# Generate points along the arc
7084
n_points = 50
7185
t = np.linspace(0, np.pi, n_points)
7286
arc_x = x_start + (x_end - x_start) * (1 - np.cos(t)) / 2
7387
arc_y = y_baseline + height * np.sin(t)
7488

75-
# Line width based on weight
76-
line_size = 1.5 + weight * 1.0
89+
line_size = 1.0 + weight * 1.2
90+
color = weight_colors[weight]
91+
alpha = weight_alphas[weight]
92+
tooltip_text = f"{nodes[start]} \u2194 {nodes[end]}"
93+
strength = weight_labels[weight]
7794

7895
for i in range(n_points):
79-
arc_data.append({"x": arc_x[i], "y": arc_y[i], "edge_id": edge_id, "weight": weight, "size": line_size})
96+
arc_data.append(
97+
{
98+
"x": arc_x[i],
99+
"y": arc_y[i],
100+
"edge_id": edge_id,
101+
"size": line_size,
102+
"color": color,
103+
"alpha": alpha,
104+
"connection": tooltip_text,
105+
"strength": strength,
106+
}
107+
)
80108

81109
arc_df = pd.DataFrame(arc_data)
82110

83-
# Node data
84-
node_df = pd.DataFrame({"x": x_positions, "y": [y_baseline] * n_nodes, "name": nodes})
111+
# Node data with size based on total connection weight (higher floor for peripheral nodes)
112+
max_conn = max(connections)
113+
node_sizes = [9 + 7 * (c / max_conn) for c in connections]
114+
node_df = pd.DataFrame(
115+
{"x": x_positions, "y": [y_baseline] * n_nodes, "name": nodes, "node_size": node_sizes, "connections": connections}
116+
)
117+
118+
# Baseline segment data
119+
baseline_df = pd.DataFrame({"x": [x_positions[0]], "xend": [x_positions[-1]], "y": [y_baseline], "yend": [y_baseline]})
85120

86121
# Label data (positioned below nodes)
87-
label_df = pd.DataFrame({"x": x_positions, "y": [y_baseline - 0.05] * n_nodes, "name": nodes})
122+
label_df = pd.DataFrame({"x": x_positions, "y": [y_baseline - 0.04] * n_nodes, "name": nodes})
123+
124+
# Legend data: small line segments showing weight encoding
125+
legend_x = 1.1
126+
legend_y_start = 0.72
127+
legend_spacing = 0.06
128+
legend_line_len = 0.07
129+
legend_lines = pd.DataFrame(
130+
{
131+
"x": [legend_x] * 3,
132+
"xend": [legend_x + legend_line_len] * 3,
133+
"y": [legend_y_start - i * legend_spacing for i in range(3)],
134+
"yend": [legend_y_start - i * legend_spacing for i in range(3)],
135+
"color": [weight_colors[3], weight_colors[2], weight_colors[1]],
136+
"size": [1.0 + 3 * 1.2, 1.0 + 2 * 1.2, 1.0 + 1 * 1.2],
137+
"alpha": [weight_alphas[3], weight_alphas[2], weight_alphas[1]],
138+
}
139+
)
140+
legend_text_df = pd.DataFrame(
141+
{
142+
"x": [legend_x + legend_line_len + 0.015] * 3,
143+
"y": [legend_y_start - i * legend_spacing for i in range(3)],
144+
"label": ["Strong (3)", "Moderate (2)", "Weak (1)"],
145+
}
146+
)
147+
legend_title_df = pd.DataFrame({"x": [legend_x], "y": [legend_y_start + 0.05], "label": ["Connection Strength"]})
88148

89-
# Create plot
149+
# Plot
90150
plot = (
91151
ggplot()
92-
# Draw arcs with semi-transparency for overlapping connections
93-
+ geom_path(data=arc_df, mapping=aes(x="x", y="y", group="edge_id", size="size"), color="#306998", alpha=0.6)
152+
# Subtle baseline
153+
+ geom_segment(data=baseline_df, mapping=aes(x="x", y="y", xend="xend", yend="yend"), color="#D0D8E0", size=0.8)
154+
# Arcs with weight-based color, transparency, and tooltips
155+
+ geom_path(
156+
data=arc_df,
157+
mapping=aes(x="x", y="y", group="edge_id", size="size", color="color", alpha="alpha"),
158+
tooltips=layer_tooltips().title("@connection").line("Strength|@strength"),
159+
)
94160
+ scale_size_identity()
95-
# Draw nodes
96-
+ geom_point(data=node_df, mapping=aes(x="x", y="y"), size=10, color="#FFD43B", fill="#FFD43B", stroke=2, shape=21)
97-
# Add node labels
161+
+ scale_color_identity()
162+
+ scale_alpha_identity()
163+
# Nodes sized by connection weight with tooltips
164+
+ geom_point(
165+
data=node_df,
166+
mapping=aes(x="x", y="y", size="node_size"),
167+
color="#1A3A5C",
168+
fill="#FFD43B",
169+
stroke=1.5,
170+
shape=21,
171+
tooltips=layer_tooltips().title("@name").line("Total weight|@connections"),
172+
)
173+
+ scale_size_identity()
174+
# Node labels
98175
+ geom_text(
99-
data=label_df, mapping=aes(x="x", y="y", label="name"), size=14, color="#306998", fontface="bold", vjust=1
176+
data=label_df, mapping=aes(x="x", y="y", label="name"), size=20, color="#1A3A5C", fontface="bold", vjust=1
177+
)
178+
# Weight legend
179+
+ geom_segment(
180+
data=legend_lines,
181+
mapping=aes(x="x", y="y", xend="xend", yend="yend", color="color", size="size", alpha="alpha"),
182+
tooltips="none",
183+
)
184+
+ geom_text(data=legend_text_df, mapping=aes(x="x", y="y", label="label"), size=16, color="#1A3A5C", hjust=0)
185+
+ geom_text(
186+
data=legend_title_df,
187+
mapping=aes(x="x", y="y", label="label"),
188+
size=16,
189+
color="#1A3A5C",
190+
fontface="bold",
191+
hjust=0,
100192
)
101193
# Styling
102-
+ xlim(-0.05, 1.05)
103-
+ ylim(-0.15, 0.85)
104-
+ labs(title="Character Interactions · arc-basic · letsplot · pyplots.ai")
194+
+ xlim(-0.05, 1.48)
195+
+ ylim(-0.01, 0.82)
196+
+ labs(
197+
title="arc-basic \u00b7 letsplot \u00b7 pyplots.ai",
198+
subtitle="Character interactions in a story chapter \u2014 node size reflects connection strength",
199+
)
105200
+ theme(
106201
axis_title=element_blank(),
107202
axis_text=element_blank(),
108203
axis_ticks=element_blank(),
109204
axis_line=element_blank(),
110205
panel_grid=element_blank(),
111-
panel_background=element_blank(),
112-
plot_title=element_text(size=24, face="bold"),
206+
panel_background=element_rect(fill="white", color="white"),
207+
plot_background=element_rect(fill="white", color="white"),
208+
plot_title=element_text(size=24, face="bold", color="#1A3A5C"),
209+
plot_subtitle=element_text(size=16, color="#4A6B82"),
113210
legend_position="none",
114211
)
115212
+ ggsize(1600, 900)
116213
)
117214

118-
# Save as PNG (scale 3x to get 4800 x 2700 px)
215+
# Save
119216
ggsave(plot, "plot.png", path=".", scale=3)
120-
121-
# Save as HTML for interactive viewing
122217
ggsave(plot, "plot.html", path=".")

0 commit comments

Comments
 (0)