Skip to content

Commit 72c5b4e

Browse files
update(arc-basic): pygal — comprehensive quality review (#4373)
## Summary Updated **pygal** 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 163e31d commit 72c5b4e

2 files changed

Lines changed: 294 additions & 56 deletions

File tree

plots/arc-basic/implementations/pygal.py

Lines changed: 72 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
""" pyplots.ai
22
arc-basic: Basic Arc Diagram
3-
Library: pygal 3.1.0 | Python 3.13.11
4-
Quality: 94/100 | Created: 2025-12-17
3+
Library: pygal 3.1.0 | Python 3.14.3
4+
Quality: 90/100 | Created: 2026-02-23
55
"""
66

77
import math
@@ -39,37 +39,53 @@
3939

4040
# Node positions along x-axis (1 to 10 range)
4141
x_positions = np.linspace(1, 10, n_nodes)
42-
y_baseline = 1.0
42+
y_baseline = 0.5
4343

44-
# Build color tuple: Python Blue for all arcs, Python Yellow for nodes
45-
n_edges = len(edges)
46-
colors = tuple(["#306998"] * n_edges + ["#FFD43B"])
44+
# Color palette: distinct hues for immediate weight differentiation (colorblind-safe)
45+
arc_colors = {1: "#93C5E8", 2: "#D4770B", 3: "#08306B"}
4746

48-
# Custom style for the chart
47+
# Thickness: wide range for immediate visual distinction
48+
arc_widths = {1: 4, 2: 12, 3: 22}
49+
50+
# Weight labels for tooltip context
51+
weight_labels = {1: "Weak", 2: "Moderate", 3: "Strong"}
52+
53+
# Build colors tuple: legend entries first, then edges, then nodes
54+
colors = tuple(
55+
[arc_colors[3], arc_colors[2], arc_colors[1]] # Legend (series 1-3)
56+
+ [arc_colors[w] for _, _, w in edges] # Edges (series 4-18)
57+
+ ["#B8860B", "#FFD43B"] # Node outline + fill
58+
)
59+
60+
# Custom style — clean white background, no borders
4961
custom_style = Style(
5062
background="white",
5163
plot_background="white",
5264
foreground="#333333",
53-
foreground_strong="#306998",
54-
foreground_subtle="#666666",
65+
foreground_strong="#08306B",
66+
foreground_subtle="transparent",
5567
colors=colors,
5668
title_font_size=72,
5769
label_font_size=40,
5870
major_label_font_size=40,
5971
legend_font_size=40,
6072
value_font_size=32,
6173
stroke_width=3,
62-
opacity=0.65,
74+
opacity=0.85,
6375
opacity_hover=1.0,
6476
)
6577

66-
# Create XY chart
78+
# Create XY chart — fill=False prevents area filling under arcs
6779
chart = pygal.XY(
6880
width=4800,
6981
height=2700,
7082
style=custom_style,
83+
fill=False,
7184
title="Character Interactions · arc-basic · pygal · pyplots.ai",
72-
show_legend=False,
85+
show_legend=True,
86+
legend_at_bottom=True,
87+
legend_at_bottom_columns=3,
88+
legend_box_size=30,
7389
x_title="",
7490
y_title="",
7591
show_x_guides=False,
@@ -78,17 +94,36 @@
7894
show_y_labels=False,
7995
stroke=True,
8096
dots_size=0,
81-
stroke_style={"width": 3, "linecap": "round"},
82-
range=(0, 6),
97+
stroke_style={"width": 6, "linecap": "round"},
98+
range=(0, 4.6),
8399
xrange=(0, 11),
84-
x_labels=nodes,
85-
x_labels_major_count=n_nodes,
100+
x_labels=[{"value": float(x_positions[i]), "label": nodes[i]} for i in range(n_nodes)],
86101
truncate_label=-1,
102+
css=[
103+
"file://style.css",
104+
"inline:.plot .background {fill: white; stroke: none !important;}",
105+
"inline:.axis .line {stroke: none !important;}",
106+
"inline:.axis .guides .line {stroke: none !important;}",
107+
"inline:.plot .axis {stroke: none !important;}",
108+
"inline:.series .line {fill: none !important;}",
109+
# Hide all legend entries after the 3 weight categories
110+
"inline:.legends > g:nth-child(n+4) {display: none !important;}",
111+
],
112+
js=[],
87113
)
88114

115+
# Add weight legend entries first (series 1-3, visible in legend)
116+
for w_val, w_label in [(3, "Strong"), (2, "Moderate"), (1, "Weak")]:
117+
chart.add(
118+
f"{w_label} connection",
119+
[None],
120+
stroke=True,
121+
show_dots=False,
122+
stroke_style={"width": arc_widths[w_val], "linecap": "round"},
123+
)
124+
89125
# Generate arc points for each edge
90-
# Each arc is drawn as a series of points forming a semi-circle
91-
arc_resolution = 30 # Number of points per arc
126+
arc_resolution = 50
92127

93128
for start_idx, end_idx, weight in edges:
94129
x_start = x_positions[start_idx]
@@ -98,49 +133,50 @@
98133
x_center = (x_start + x_end) / 2
99134
arc_radius = abs(x_end - x_start) / 2
100135

101-
# Arc height proportional to the distance between nodes
136+
# Arc height proportional to node distance
102137
distance = abs(end_idx - start_idx)
103138
height_scale = 0.4 * distance
104139

105140
# Generate arc points (semi-circle above baseline)
106141
arc_points = []
107142
for i in range(arc_resolution + 1):
108-
theta = math.pi * i / arc_resolution # 0 to pi
143+
theta = math.pi * i / arc_resolution
109144
x = x_center - arc_radius * math.cos(theta)
110145
y = y_baseline + height_scale * math.sin(theta)
111-
arc_points.append((x, y))
146+
arc_points.append(
147+
{"value": (x, y), "label": f"{nodes[start_idx]}{nodes[end_idx]} ({weight_labels[weight]})"}
148+
)
112149

113-
# Line thickness based on weight
114150
chart.add(
115-
f"Arc {start_idx}-{end_idx}",
116-
arc_points,
117-
stroke=True,
118-
show_dots=False,
119-
fill=False,
120-
stroke_style={"width": 2 + weight * 2, "linecap": "round"},
151+
"", arc_points, stroke=True, show_dots=False, stroke_style={"width": arc_widths[weight], "linecap": "round"}
121152
)
122153

123-
# Add nodes as a separate series (last, so uses Python Yellow)
124-
node_points = []
125-
for i, name in enumerate(nodes):
126-
x = x_positions[i]
127-
node_points.append({"value": (x, y_baseline), "label": name})
154+
# Add node outline ring (dark goldenrod border effect)
155+
node_points = [
156+
{
157+
"value": (float(x_positions[i]), y_baseline),
158+
"label": f"{nodes[i]} ({sum(1 for s, t, _ in edges if s == i or t == i)} connections)",
159+
}
160+
for i in range(n_nodes)
161+
]
162+
chart.add("", node_points, stroke=False, dots_size=42)
128163

129-
chart.add("Characters", node_points, stroke=False, dots_size=35)
164+
# Add node fill on top (Python Yellow)
165+
chart.add("", node_points, stroke=False, dots_size=32)
130166

131167
# Save outputs
132168
chart.render_to_file("plot.svg")
133169
chart.render_to_png("plot.png")
134170

135-
# Also save HTML for interactive version
171+
# Save HTML for interactive version with hover tooltips
136172
with open("plot.html", "w") as f:
137173
f.write(
138174
"""<!DOCTYPE html>
139175
<html>
140176
<head>
141177
<title>Character Interactions · arc-basic · pygal · pyplots.ai</title>
142178
<style>
143-
body { margin: 0; padding: 20px; background: #f5f5f5; }
179+
body { margin: 0; padding: 20px; background: #f5f5f5; font-family: sans-serif; }
144180
.container { max-width: 100%; margin: 0 auto; }
145181
object { width: 100%; height: auto; }
146182
</style>

0 commit comments

Comments
 (0)