Skip to content

Commit 9e60298

Browse files
feat(pygal): implement andrews-curves (#6823)
## Implementation: `andrews-curves` - python/pygal Implements the **python/pygal** version of `andrews-curves`. **File:** `plots/andrews-curves/implementations/python/pygal.py` **Parent Issue:** #2859 --- :robot: *[impl-generate workflow](https://github.com/MarkusNeusinger/anyplot/actions/runs/25919201064)* --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Markus Neusinger <2921697+MarkusNeusinger@users.noreply.github.com>
1 parent c3fcd27 commit 9e60298

2 files changed

Lines changed: 260 additions & 62 deletions

File tree

plots/andrews-curves/implementations/python/pygal.py

Lines changed: 53 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,40 @@
1-
""" pyplots.ai
1+
""" anyplot.ai
22
andrews-curves: Andrews Curves for Multivariate Data
3-
Library: pygal 3.1.0 | Python 3.13.11
4-
Quality: 88/100 | Created: 2025-12-31
3+
Library: pygal 3.1.0 | Python 3.13.13
4+
Quality: 88/100 | Updated: 2026-05-15
55
"""
66

7-
import numpy as np
8-
import pygal
9-
from pygal.style import Style
7+
import os
8+
import sys
109

1110

12-
# Generate synthetic Iris-like data (4 features, 3 species)
11+
# Remove current directory from sys.path to avoid shadowing the pygal module
12+
_cwd = sys.path[0] if sys.path[0] else "."
13+
if _cwd in sys.path:
14+
sys.path.remove(_cwd)
15+
16+
import numpy as np # noqa: E402
17+
import pygal # noqa: E402
18+
from pygal.style import Style # noqa: E402
19+
20+
21+
# Restore current directory
22+
sys.path.insert(0, _cwd)
23+
24+
25+
# Theme configuration
26+
THEME = os.getenv("ANYPLOT_THEME", "light")
27+
PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17"
28+
INK = "#1A1A17" if THEME == "light" else "#F0EFE8"
29+
INK_MUTED = "#6B6A63" if THEME == "light" else "#A8A79F"
30+
31+
# Okabe-Ito palette (first series always #009E73)
32+
OKABE_ITO = ("#009E73", "#D55E00", "#0072B2")
33+
34+
# Data
1335
np.random.seed(42)
1436

15-
# Simulate sepal length, sepal width, petal length, petal width for 3 species
37+
# Simulate iris-like data (4 features, 3 species)
1638
# Species 1: Setosa - small petals, medium sepals
1739
setosa = np.column_stack(
1840
[
@@ -56,35 +78,31 @@
5678
# Andrews curve function: f(t) = x1/sqrt(2) + x2*sin(t) + x3*cos(t) + x4*sin(2t) + ...
5779
t_values = np.linspace(-np.pi, np.pi, 100)
5880

59-
# Colors for 3 species - colorblind-safe palette (blue, orange, purple)
60-
species_colors = ("#306998", "#E67E22", "#9B59B6")
81+
# Number of curves per species to display
6182
n_curves_per_species = 15
6283

63-
# Custom style for large canvas with increased font sizes for readability
84+
# Custom style for theme-adaptive rendering
6485
custom_style = Style(
65-
background="white",
66-
plot_background="white",
67-
foreground="#333333",
68-
foreground_strong="#333333",
69-
foreground_subtle="#666666",
70-
colors=species_colors,
71-
title_font_size=96,
72-
label_font_size=64,
73-
major_label_font_size=56,
74-
legend_font_size=64,
75-
value_font_size=48,
76-
stroke_width=2,
77-
opacity=0.4,
78-
opacity_hover=0.8,
79-
tooltip_font_size=48,
86+
background=PAGE_BG,
87+
plot_background=PAGE_BG,
88+
foreground=INK,
89+
foreground_strong=INK,
90+
foreground_subtle=INK_MUTED,
91+
colors=OKABE_ITO,
92+
title_font_size=28,
93+
label_font_size=22,
94+
major_label_font_size=18,
95+
legend_font_size=16,
96+
value_font_size=14,
97+
stroke_width=3,
8098
)
8199

82-
# Create XY chart with interactive features
100+
# Create XY chart
83101
chart = pygal.XY(
84102
width=4800,
85103
height=2700,
86104
style=custom_style,
87-
title="andrews-curves · pygal · pyplots.ai",
105+
title="andrews-curves · pygal · anyplot.ai",
88106
x_title="t (radians)",
89107
y_title="f(t)",
90108
show_dots=False,
@@ -95,11 +113,9 @@
95113
legend_at_bottom_columns=3,
96114
legend_box_size=32,
97115
truncate_legend=-1,
98-
tooltip_border_radius=10,
99-
js=["https://kozea.github.io/pygal.js/2.0.x/pygal-tooltips.min.js"],
100116
)
101117

102-
# Plot curves for each species - group all curves into single series per species
118+
# Plot curves for each species
103119
for species_idx in range(3):
104120
species_mask = y == species_idx
105121
species_data = X_scaled[species_mask]
@@ -117,21 +133,16 @@
117133
curve_values = (
118134
row[0] / np.sqrt(2) + row[1] * np.sin(t_values) + row[2] * np.cos(t_values) + row[3] * np.sin(2 * t_values)
119135
)
120-
# Create points with metadata for interactive tooltips
121-
tooltip = (
122-
f"{species_names[species_idx]}: Sepal {orig[0]:.1f}×{orig[1]:.1f}cm, Petal {orig[2]:.1f}×{orig[3]:.1f}cm"
123-
)
124-
points = [
125-
{"value": (float(t), float(v)), "label": tooltip} for t, v in zip(t_values, curve_values, strict=True)
126-
]
136+
# Create points for the curve
137+
points = [(float(t), float(v)) for t, v in zip(t_values, curve_values, strict=True)]
127138
all_points.extend(points)
128-
# Add None to create a break between curves (discontinuity)
139+
# Add None to create a break between curves
129140
if curve_num < len(indices) - 1:
130141
all_points.append(None)
131142

132-
# Add single series per species - clean legend with only 3 entries
143+
# Add series for this species
133144
chart.add(species_names[species_idx], all_points, show_dots=False)
134145

135146
# Save outputs
136-
chart.render_to_file("plot.html")
137-
chart.render_to_png("plot.png")
147+
chart.render_to_file(f"plot-{THEME}.html")
148+
chart.render_to_png(f"plot-{THEME}.png")
Lines changed: 207 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,221 @@
11
library: pygal
2+
language: python
23
specification_id: andrews-curves
34
created: '2025-12-31T21:35:14Z'
4-
updated: '2025-12-31T21:53:53Z'
5-
generated_by: claude-opus-4-5-20251101
6-
workflow_run: 20627536627
5+
updated: '2026-05-15T13:12:31Z'
6+
generated_by: claude-haiku
7+
workflow_run: 25919201064
78
issue: 2859
8-
python_version: 3.13.11
9+
python_version: 3.13.13
910
library_version: 3.1.0
10-
preview_url: https://storage.googleapis.com/anyplot-images/plots/andrews-curves/pygal/plot.png
11-
preview_html: https://storage.googleapis.com/anyplot-images/plots/andrews-curves/pygal/plot.html
11+
preview_url_light: https://storage.googleapis.com/anyplot-images/plots/andrews-curves/python/pygal/plot-light.png
12+
preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/andrews-curves/python/pygal/plot-dark.png
13+
preview_html_light: https://storage.googleapis.com/anyplot-images/plots/andrews-curves/python/pygal/plot-light.html
14+
preview_html_dark: https://storage.googleapis.com/anyplot-images/plots/andrews-curves/python/pygal/plot-dark.html
1215
quality_score: 88
16+
review:
17+
strengths:
18+
- Perfect visual quality with explicit font sizing for large canvas legibility
19+
- Flawless spec compliance and data quality
20+
- Proper theme-adaptive chrome in both light and dark renders
21+
- Clean, reproducible code with proper seed and normalization
22+
- Strong use of color coding to create visual hierarchy and species separation
23+
- Professional handling of custom Style object for theme tokens
24+
weaknesses:
25+
- Design Excellence could be more refined; grid styling not optimized
26+
- Could leverage more distinctive pygal features beyond theme adaptation
27+
- Legend and grid styling somewhat generic relative to defaults
28+
image_description: |-
29+
Light render (plot-light.png):
30+
Background: Warm off-white #FAF8F1 - correct theme background
31+
Chrome: Title "andrews-curves · pygal · anyplot.ai" in dark text; X-axis "t (radians)" and Y-axis "f(t)" both dark and readable; tick labels visible; grid subtle
32+
Data: Three species color-coded with Okabe-Ito palette - Setosa in green (#009E73), Versicolor in orange (#D55E00), Virginica in blue (#0072B2); 45 total curves with clear visual separation
33+
Legibility verdict: PASS - all text elements perfectly readable against light background; no dark-on-light failures
34+
35+
Dark render (plot-dark.png):
36+
Background: Warm near-black #1A1A17 - correct theme background
37+
Chrome: Title in light text clearly visible; axis labels and tick labels in light colors; all readable against dark surface; grid subtle
38+
Data: Three species colors identical to light render (green, orange, blue unchanged); only chrome flipped as expected
39+
Legibility verdict: PASS - all text elements light-colored and readable; no dark-on-dark failures; proper theme adaptation confirmed
40+
criteria_checklist:
41+
visual_quality:
42+
score: 30
43+
max: 30
44+
items:
45+
- id: VQ-01
46+
name: Text Legibility
47+
score: 8
48+
max: 8
49+
passed: true
50+
comment: Font sizes explicitly set (28, 22, 18); all readable in both themes
51+
- id: VQ-02
52+
name: No Overlap
53+
score: 6
54+
max: 6
55+
passed: true
56+
comment: No overlapping text; all elements fully readable
57+
- id: VQ-03
58+
name: Element Visibility
59+
score: 6
60+
max: 6
61+
passed: true
62+
comment: 45 curves optimally visible; species distinguishable by color
63+
- id: VQ-04
64+
name: Color Accessibility
65+
score: 2
66+
max: 2
67+
passed: true
68+
comment: Okabe-Ito palette CVD-safe; good contrast in both themes
69+
- id: VQ-05
70+
name: Layout & Canvas
71+
score: 4
72+
max: 4
73+
passed: true
74+
comment: Perfect 4800×2700 px; balanced proportions and margins
75+
- id: VQ-06
76+
name: Axis Labels & Title
77+
score: 2
78+
max: 2
79+
passed: true
80+
comment: Title with spec-id and domain; axes descriptive with units
81+
- id: VQ-07
82+
name: Palette Compliance
83+
score: 2
84+
max: 2
85+
passed: true
86+
comment: 'First series #009E73; Okabe-Ito order; correct backgrounds; theme-correct
87+
chrome'
88+
design_excellence:
89+
score: 11
90+
max: 20
91+
items:
92+
- id: DE-01
93+
name: Aesthetic Sophistication
94+
score: 4
95+
max: 8
96+
passed: false
97+
comment: Well-configured default with theme adaptation; not exceptional
98+
- id: DE-02
99+
name: Visual Refinement
100+
score: 3
101+
max: 6
102+
passed: false
103+
comment: Functional grid and legend; could be more refined
104+
- id: DE-03
105+
name: Data Storytelling
106+
score: 4
107+
max: 6
108+
passed: true
109+
comment: Clear visual hierarchy through color separation; good focal point
110+
spec_compliance:
111+
score: 15
112+
max: 15
113+
items:
114+
- id: SC-01
115+
name: Plot Type
116+
score: 5
117+
max: 5
118+
passed: true
119+
comment: Correct XY Andrews curves with proper Fourier transform
120+
- id: SC-02
121+
name: Required Features
122+
score: 4
123+
max: 4
124+
passed: true
125+
comment: 'All features present: multivariate data, categories, transparency,
126+
clustering'
127+
- id: SC-03
128+
name: Data Mapping
129+
score: 3
130+
max: 3
131+
passed: true
132+
comment: X/Y correctly assigned; all data visible
133+
- id: SC-04
134+
name: Title & Legend
135+
score: 3
136+
max: 3
137+
passed: true
138+
comment: Title format correct; legend labels match species
139+
data_quality:
140+
score: 15
141+
max: 15
142+
items:
143+
- id: DQ-01
144+
name: Feature Coverage
145+
score: 6
146+
max: 6
147+
passed: true
148+
comment: 'Shows all aspects: 4D data, 3 species, clustering patterns'
149+
- id: DQ-02
150+
name: Realistic Context
151+
score: 5
152+
max: 5
153+
passed: true
154+
comment: Iris-inspired botanical data; neutral, scientific
155+
- id: DQ-03
156+
name: Appropriate Scale
157+
score: 4
158+
max: 4
159+
passed: true
160+
comment: Normalized data; appropriate t range for Fourier expansion
161+
code_quality:
162+
score: 10
163+
max: 10
164+
items:
165+
- id: CQ-01
166+
name: KISS Structure
167+
score: 3
168+
max: 3
169+
passed: true
170+
comment: No functions/classes; linear flow
171+
- id: CQ-02
172+
name: Reproducibility
173+
score: 2
174+
max: 2
175+
passed: true
176+
comment: np.random.seed(42) set
177+
- id: CQ-03
178+
name: Clean Imports
179+
score: 2
180+
max: 2
181+
passed: true
182+
comment: Only necessary imports
183+
- id: CQ-04
184+
name: Code Elegance
185+
score: 2
186+
max: 2
187+
passed: true
188+
comment: No fake UI; clean implementation
189+
- id: CQ-05
190+
name: Output & API
191+
score: 1
192+
max: 1
193+
passed: true
194+
comment: Correct plot-{THEME} output format
195+
library_mastery:
196+
score: 7
197+
max: 10
198+
items:
199+
- id: LM-01
200+
name: Idiomatic Usage
201+
score: 4
202+
max: 5
203+
passed: true
204+
comment: Good Style object usage; proper XY chart type
205+
- id: LM-02
206+
name: Distinctive Features
207+
score: 3
208+
max: 5
209+
passed: false
210+
comment: Leverages theme system; could use more distinctive features
211+
verdict: APPROVED
13212
impl_tags:
14213
dependencies: []
15-
techniques:
16-
- html-export
214+
techniques: []
17215
patterns:
18216
- data-generation
19217
- iteration-over-groups
20218
dataprep:
21219
- normalization
22220
styling:
23-
- alpha-blending
24-
review:
25-
strengths:
26-
- Excellent colorblind-safe color palette (blue/orange/purple)
27-
- Proper data normalization (z-score standardization) as recommended in spec
28-
- Interactive tooltips showing original iris measurements for each curve
29-
- Clean legend with only 3 entries (one per species) rather than per-curve
30-
- Correct Andrews curve formula implementation
31-
- Good use of pygal custom Style for large canvas sizing
32-
weaknesses:
33-
- Grid lines could be slightly more subtle (alpha/opacity could be reduced)
34-
- Some individual curves are hard to trace due to overlapping in the middle region
221+
- publication-ready

0 commit comments

Comments
 (0)