diff --git a/plots/circos-basic/implementations/python/pygal.py b/plots/circos-basic/implementations/python/pygal.py new file mode 100644 index 0000000000..34b0290238 --- /dev/null +++ b/plots/circos-basic/implementations/python/pygal.py @@ -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()) diff --git a/plots/circos-basic/metadata/python/pygal.yaml b/plots/circos-basic/metadata/python/pygal.yaml new file mode 100644 index 0000000000..b6ba4c8755 --- /dev/null +++ b/plots/circos-basic/metadata/python/pygal.yaml @@ -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: []