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
127 changes: 127 additions & 0 deletions plots/circos-basic/implementations/python/pygal.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
""" anyplot.ai
circos-basic: Circos Plot
Library: pygal 3.1.0 | Python 3.13.13
Quality: 91/100 | Created: 2026-05-15
"""

import math
import os
import sys

import numpy as np


# Handle the name conflict: load pygal from site-packages first
# Remove current directory from path to prevent the script from shadowing pygal module
original_path = sys.path.copy()
if "" in sys.path:
sys.path.remove("")
if "." in sys.path:
sys.path.remove(".")

# Now safe to import pygal
import pygal # noqa: E402
from pygal.style import Style # noqa: E402


# Restore path for other operations
sys.path = original_path


THEME = os.getenv("ANYPLOT_THEME", "light")
PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17"
INK = "#1A1A17" if THEME == "light" else "#F0EFE8"
INK_MUTED = "#6B6A63" if THEME == "light" else "#A8A79F"

OKABE_ITO = ("#009E73", "#D55E00", "#0072B2", "#CC79A7", "#E69F00", "#56B4E9", "#F0E442")

custom_style = Style(
background=PAGE_BG,
plot_background=PAGE_BG,
foreground=INK,
foreground_strong=INK,
foreground_subtle=INK_MUTED,
colors=OKABE_ITO,
title_font_size=28,
label_font_size=22,
major_label_font_size=18,
legend_font_size=16,
value_font_size=14,
stroke_width=3,
)

# Data: genomic segments with inter-chromosomal connections
np.random.seed(42)
segments = ["Chr1", "Chr2", "Chr3", "Chr4", "Chr5", "Chr6", "Chr7", "Chr8", "Chr9", "Chr10"]
n_segments = len(segments)

# Calculate angle for each segment
segment_angles = np.linspace(0, 2 * np.pi, n_segments, endpoint=False)

# Create XY scatter plot arranged in circle
chart = pygal.XY(
style=custom_style,
width=4800,
height=2700,
title="circos-basic · pygal · anyplot.ai",
x_title="",
y_title="",
show_legend=True,
dots_size=6,
stroke_style={"width": 3},
)

chart.x_labels = []
chart.y_labels = []

outer_radius = 90

# Create segment points on outer circle
segment_points = []
for i, (angle, segment) in enumerate(zip(segment_angles, segments)): # noqa: B905
x = outer_radius * math.cos(angle)
y = outer_radius * math.sin(angle)
segment_points.append((x, y, segment, OKABE_ITO[i % len(OKABE_ITO)]))

# Add segment positions as points
for x, y, segment, _color in segment_points:
chart.add(segment, [(x, y)], dots_size=10, allow_interruptions=True)

# Create connections between segments
connections = [
(0, 3, 45),
(1, 4, 60),
(2, 5, 55),
(3, 6, 40),
(4, 7, 65),
(5, 8, 50),
(6, 9, 35),
(0, 5, 70),
(1, 8, 48),
(2, 7, 52),
(3, 9, 38),
]

# Create arc points for connections
for source_idx, target_idx, magnitude in connections:
source_x, source_y, _, _ = segment_points[source_idx]
target_x, target_y, _, _ = segment_points[target_idx]

curve_factor = 0.3 + (magnitude / 100) * 0.4
inner_radius = outer_radius * (1 - curve_factor)

arc_points = []
for t in np.linspace(0, 1, 20):
angle_interp = segment_angles[source_idx] * (1 - t) + segment_angles[target_idx] * t
radius_t = outer_radius - ((outer_radius - inner_radius) * 4 * t * (1 - t))
x = radius_t * math.cos(angle_interp)
y = radius_t * math.sin(angle_interp)
arc_points.append((x, y))

chart.add("", arc_points, show_legend=False, dots_size=0, allow_interruptions=True)

# Render outputs
chart.render_to_png(f"plot-{THEME}.png")

with open(f"plot-{THEME}.html", "wb") as f:
f.write(chart.render())
223 changes: 223 additions & 0 deletions plots/circos-basic/metadata/python/pygal.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
library: pygal
language: python
specification_id: circos-basic
created: '2026-05-15T09:31:54Z'
updated: '2026-05-15T09:48:53Z'
generated_by: claude-haiku
workflow_run: 25910607816
issue: 3005
python_version: 3.13.13
library_version: 3.1.0
preview_url_light: https://storage.googleapis.com/anyplot-images/plots/circos-basic/python/pygal/plot-light.png
preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/circos-basic/python/pygal/plot-dark.png
preview_html_light: https://storage.googleapis.com/anyplot-images/plots/circos-basic/python/pygal/plot-light.html
preview_html_dark: https://storage.googleapis.com/anyplot-images/plots/circos-basic/python/pygal/plot-dark.html
quality_score: 91
review:
strengths:
- Excellent visual quality with properly sized and readable text in both themes
- 'Perfect palette compliance: Okabe-Ito colors identical across light/dark, theme-correct
chrome'
- Clean, elegant circular design with effective visual hierarchy
- Solid code structure with proper theme token integration and reproducibility
- 'Comprehensive spec compliance: all required features present and correctly implemented'
weaknesses:
- Library mastery could explore more sophisticated pygal features beyond basic API
usage
image_description: |-
Light render (plot-light.png):
Background: Warm off-white (#FAF8F1) - correct light theme surface
Chrome: Title "circos-basic · pygal · anyplot.ai" in dark text (#1A1A17), legend showing segment names - all readable
Data: 10 chromosome segments as colored dots in circular arrangement, 11 curved connections between segments. First series is green (#009E73), followed by Okabe-Ito colors in order
Legibility verdict: PASS - all text clearly readable, no contrast issues

Dark render (plot-dark.png):
Background: Warm near-black (#1A1A17) - correct dark theme surface
Chrome: Title and legend in light text (#F0EFE8) - all clearly readable against dark background, no dark-on-dark issues
Data: Identical segment and connection colors to light render - Okabe-Ito palette preserved, only chrome flips to light colors
Legibility verdict: PASS - excellent readability in dark theme, proper contrast throughout
criteria_checklist:
visual_quality:
score: 30
max: 30
items:
- id: VQ-01
name: Text Legibility
score: 8
max: 8
passed: true
comment: All text explicitly sized and readable in both themes
- id: VQ-02
name: No Overlap
score: 6
max: 6
passed: true
comment: No overlapping text elements
- id: VQ-03
name: Element Visibility
score: 6
max: 6
passed: true
comment: All dots and curves clearly visible
- id: VQ-04
name: Color Accessibility
score: 2
max: 2
passed: true
comment: Okabe-Ito palette is colorblind-safe
- id: VQ-05
name: Layout & Canvas
score: 4
max: 4
passed: true
comment: Perfect 4800x2700 landscape layout with balanced margins
- id: VQ-06
name: Axis Labels & Title
score: 2
max: 2
passed: true
comment: Correct title format, no axes needed for circos
- id: VQ-07
name: Palette Compliance
score: 2
max: 2
passed: true
comment: 'Okabe-Ito with #009E73 first series, correct backgrounds and chrome'
design_excellence:
score: 14
max: 20
items:
- id: DE-01
name: Aesthetic Sophistication
score: 6
max: 8
passed: true
comment: Professional, thoughtful palette application - above defaults but
not FiveThirtyEight level
- id: DE-02
name: Visual Refinement
score: 4
max: 6
passed: true
comment: Good refinement with theme tokens and whitespace, could add more
polish
- id: DE-03
name: Data Storytelling
score: 4
max: 6
passed: true
comment: Clear visual hierarchy with segments and connections creating clear
focal point
spec_compliance:
score: 15
max: 15
items:
- id: SC-01
name: Plot Type
score: 5
max: 5
passed: true
comment: Correct circos plot
- id: SC-02
name: Required Features
score: 4
max: 4
passed: true
comment: 'All features: segments, connections, magnitudes, colors'
- id: SC-03
name: Data Mapping
score: 3
max: 3
passed: true
comment: Correct positioning and connection mapping
- id: SC-04
name: Title & Legend
score: 3
max: 3
passed: true
comment: Correct title format and legend labels
data_quality:
score: 15
max: 15
items:
- id: DQ-01
name: Feature Coverage
score: 6
max: 6
passed: true
comment: 'Shows all aspects: segments with variety, connections with varying
magnitudes'
- id: DQ-02
name: Realistic Context
score: 5
max: 5
passed: true
comment: Authentic genomic data with standard chromosome naming
- id: DQ-03
name: Appropriate Scale
score: 4
max: 4
passed: true
comment: Realistic proportions for genomic analysis
code_quality:
score: 10
max: 10
items:
- id: CQ-01
name: KISS Structure
score: 3
max: 3
passed: true
comment: Linear flow, no unnecessary abstractions
- id: CQ-02
name: Reproducibility
score: 2
max: 2
passed: true
comment: Seed set, deterministic output
- id: CQ-03
name: Clean Imports
score: 2
max: 2
passed: true
comment: All imports used
- id: CQ-04
name: Code Elegance
score: 2
max: 2
passed: true
comment: Clean, Pythonic, no fake UI
- id: CQ-05
name: Output & API
score: 1
max: 1
passed: true
comment: Correct PNG and HTML output
library_mastery:
score: 7
max: 10
items:
- id: LM-01
name: Idiomatic Usage
score: 4
max: 5
passed: true
comment: Correct Style and XY API usage, proper sizing
- id: LM-02
name: Distinctive Features
score: 3
max: 5
passed: true
comment: Creative parametric curve approach for circos, functional but not
showcase-level
verdict: APPROVED
impl_tags:
dependencies: []
techniques:
- layer-composition
- html-export
patterns:
- data-generation
- iteration-over-groups
dataprep: []
styling: []
Loading