diff --git a/plots/manhattan-gwas/implementations/python/pygal.py b/plots/manhattan-gwas/implementations/python/pygal.py index 541d55a918..f2f3943827 100644 --- a/plots/manhattan-gwas/implementations/python/pygal.py +++ b/plots/manhattan-gwas/implementations/python/pygal.py @@ -1,18 +1,27 @@ -""" pyplots.ai +""" anyplot.ai manhattan-gwas: Manhattan Plot for GWAS -Library: pygal 3.1.0 | Python 3.13.11 -Quality: 82/100 | Created: 2025-12-31 +Library: pygal 3.1.0 | Python 3.13.13 +Quality: 89/100 | Updated: 2026-05-15 """ +import os + import numpy as np import pygal from pygal.style import Style -# Seed for reproducibility np.random.seed(42) -# Chromosome lengths (simplified, in Mb scale) +THEME = os.getenv("ANYPLOT_THEME", "light") +PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17" +INK = "#1A1A17" if THEME == "light" else "#F0EFE8" +INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0" +INK_MUTED = "#6B6A63" if THEME == "light" else "#A8A79F" + +OKABE_ITO = ("#009E73", "#D55E00", "#0072B2", "#CC79A7", "#E69F00", "#56B4E9", "#F0E442") + +# Chromosome lengths (simplified, in Mb) chrom_lengths = { "1": 249, "2": 243, @@ -85,26 +94,19 @@ genome_wide_sig = -np.log10(5e-8) # ~7.3 suggestive_sig = 5.0 # -log10(1e-5) -# Custom style with explicit colors for threshold lines +# Custom style with theme-adaptive colors custom_style = Style( - background="white", - plot_background="white", - foreground="#333333", - foreground_strong="#333333", - foreground_subtle="#666666", - colors=( - "#306998", # Blue - odd chromosomes - "#888888", # Gray - even chromosomes - "#D62728", # Red - significant points above threshold - "#D62728", # Red - genome-wide threshold line - "#FF7F0E", # Orange - suggestive threshold line - ), - title_font_size=56, - label_font_size=36, - major_label_font_size=32, - legend_font_size=32, - value_font_size=20, - font_family="sans-serif", + 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, ) # Create XY scatter chart @@ -112,15 +114,15 @@ width=4800, height=2700, style=custom_style, - title="manhattan-gwas · pygal · pyplots.ai", + title="manhattan-gwas · pygal · anyplot.ai", x_title="Chromosome", y_title="-log₁₀(p-value)", show_legend=True, legend_at_bottom=True, legend_at_bottom_columns=5, - legend_box_size=24, + legend_box_size=16, stroke=False, - dots_size=5, + dots_size=4, show_x_guides=False, show_y_guides=True, truncate_label=-1, @@ -128,50 +130,41 @@ x_label_rotation=0, range=(0, 16), xrange=(0, cumulative_offset), - tooltip_border_radius=10, + tooltip_border_radius=5, explicit_size=True, - spacing=30, - margin=60, - margin_bottom=200, - margin_top=100, + spacing=20, + margin=50, + margin_bottom=150, + margin_top=80, ) -# Create x_labels with chromosome numbers at midpoints -# Use string labels for chromosomes 1-22 -chart.x_labels = [str(i + 1) for i in range(len(chromosomes))] -chart.x_labels_major_count = len(chromosomes) - -# Map positions to display label indices -# We need to normalize x values to show chromosome labels -# Pygal XY chart needs numeric x_labels for scatter, so we'll use custom formatter -label_positions = chrom_midpoints.copy() +# Set x-axis labels at chromosome midpoints +chart.x_labels = chrom_midpoints def format_x_label(x_val): """Find closest chromosome midpoint and return chromosome number.""" - if not label_positions: + if not chrom_midpoints: return "" min_dist = float("inf") closest_idx = 0 - for i, pos in enumerate(label_positions): + for i, pos in enumerate(chrom_midpoints): dist = abs(x_val - pos) if dist < min_dist: min_dist = dist closest_idx = i - # Only return label if close to midpoint (within half chromosome width) + # Only return label if close to midpoint if min_dist < 50: return str(closest_idx + 1) return "" -# Set numeric x_labels at chromosome midpoints -chart.x_labels = chrom_midpoints -chart.x_value_formatter = lambda x: format_x_label(x) if x in chrom_midpoints else "" +chart.x_value_formatter = lambda x: format_x_label(x) # Prepare data by chromosome with alternating colors odd_chrom_points = [] even_chrom_points = [] -significant_points = [] # Points above genome-wide significance +significant_points = [] for idx, chrom in enumerate(chromosomes): chrom_mask = [c == chrom for c in all_chroms] @@ -187,26 +180,21 @@ def format_x_label(x_val): else: even_chrom_points.append(point) -# Add chromosome data series (blue for odd, gray for even) +# Add data series chart.add("Odd chromosomes", odd_chrom_points, stroke=False, show_dots=True) chart.add("Even chromosomes", even_chrom_points, stroke=False, show_dots=True) - -# Add significant points as separate series (highlighted) chart.add("Significant (p<5×10⁻⁸)", significant_points, stroke=False, show_dots=True) -# Add threshold lines as dense scatter points (works better in PNG than stroke) -# Using many small dots to create visible line effect +# Add threshold lines n_line_points = 200 threshold_x = np.linspace(10, cumulative_offset - 10, n_line_points) -# Genome-wide significance threshold line (y ≈ 7.3) gw_line_points = [ {"value": (x, genome_wide_sig), "label": f"Genome-wide threshold: -log₁₀(5×10⁻⁸) = {genome_wide_sig:.1f}"} for x in threshold_x ] chart.add("p = 5×10⁻⁸ threshold", gw_line_points, stroke=True, show_dots=True, dots_size=2) -# Suggestive threshold line (y = 5) sugg_line_points = [ {"value": (x, suggestive_sig), "label": f"Suggestive threshold: -log₁₀(1×10⁻⁵) = {suggestive_sig:.1f}"} for x in threshold_x @@ -214,5 +202,5 @@ def format_x_label(x_val): chart.add("p = 1×10⁻⁵ threshold", sugg_line_points, stroke=True, show_dots=True, dots_size=2) # Save outputs -chart.render_to_file("plot.html") -chart.render_to_png("plot.png") +chart.render_to_file(f"plot-{THEME}.html") +chart.render_to_png(f"plot-{THEME}.png") diff --git a/plots/manhattan-gwas/metadata/python/pygal.yaml b/plots/manhattan-gwas/metadata/python/pygal.yaml index 42b84af948..0a15f458dc 100644 --- a/plots/manhattan-gwas/metadata/python/pygal.yaml +++ b/plots/manhattan-gwas/metadata/python/pygal.yaml @@ -1,40 +1,222 @@ library: pygal +language: python specification_id: manhattan-gwas created: '2025-12-31T05:37:41Z' -updated: '2025-12-31T15:21:52Z' -generated_by: claude-opus-4-5-20251101 -workflow_run: 20612794558 +updated: '2026-05-15T03:44:18Z' +generated_by: claude-haiku +workflow_run: 25898709547 issue: 2925 -python_version: 3.13.11 +python_version: 3.13.13 library_version: 3.1.0 -preview_url: https://storage.googleapis.com/anyplot-images/plots/manhattan-gwas/pygal/plot.png -preview_html: https://storage.googleapis.com/anyplot-images/plots/manhattan-gwas/pygal/plot.html -quality_score: 82 +preview_url_light: https://storage.googleapis.com/anyplot-images/plots/manhattan-gwas/python/pygal/plot-light.png +preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/manhattan-gwas/python/pygal/plot-dark.png +preview_html_light: https://storage.googleapis.com/anyplot-images/plots/manhattan-gwas/python/pygal/plot-light.html +preview_html_dark: https://storage.googleapis.com/anyplot-images/plots/manhattan-gwas/python/pygal/plot-dark.html +quality_score: 89 +review: + strengths: + - Proper theme adaptation with all required theme tokens (PAGE_BG, INK, INK_SOFT, + INK_MUTED) + - Correct Okabe-Ito color palette with first series as brand green (#009E73) + - Both light and dark renders are fully readable with no legibility failures + - Clean visualization with effective alternating chromosome coloring + - Both significance thresholds properly marked at correct p-value levels + - Data colors remain identical between themes; only chrome adapts appropriately + - Reproducible output with fixed random seed + weaknesses: [] + image_description: |- + Light render (plot-light.png): + Background: Warm off-white (#FAF8F1) — correct per style guide + Chrome: Title, axis labels (X: "Chromosome", Y: "-log₁₀(p-value)"), tick labels all in dark text (#1A1A17) — clearly readable + Data: Alternating teal/green (#009E73 - Okabe-Ito position 1) and orange (#D55E00 - position 2) for odd/even chromosomes. Significant peaks in blue (#0072B2). Two dashed threshold lines at y=5 and y≈7.3. All elements clearly distinguishable. + Legibility verdict: PASS — all text and data elements are readable against light background + + Dark render (plot-dark.png): + Background: Warm near-black (#1A1A17) — correct per style guide + Chrome: Title, axis labels, tick labels all in light text (#F0EFE8) — clearly readable against dark background. No "dark-on-dark" failures. + Data: Identical colors to light render — same greens and oranges for chromosome alternation, same blue for significant peaks. Threshold lines visible. All elements distinguishable from dark background. + Legibility verdict: PASS — all text is readable against dark background, data colors maintain identity from light render, no theme-adaptation failures + criteria_checklist: + visual_quality: + score: 30 + max: 30 + items: + - id: VQ-01 + name: Text Legibility + score: 8 + max: 8 + passed: true + comment: All text elements clearly readable in both themes at full size + - id: VQ-02 + name: No Overlap + score: 6 + max: 6 + passed: true + comment: No text overlap, chromosome labels at midpoints, legend positioned + cleanly + - id: VQ-03 + name: Element Visibility + score: 6 + max: 6 + passed: true + comment: All markers visible even in dense regions, significant peaks stand + out + - id: VQ-04 + name: Color Accessibility + score: 2 + max: 2 + passed: true + comment: Okabe-Ito palette is colorblind-safe, no red-green as sole signal + - id: VQ-05 + name: Layout & Canvas + score: 4 + max: 4 + passed: true + comment: Balanced proportions, generous margins, nothing cut off + - id: VQ-06 + name: Axis Labels & Title + score: 2 + max: 2 + passed: true + comment: Title correct format, axes labeled with units + - id: VQ-07 + name: Palette Compliance + score: 2 + max: 2 + passed: true + comment: 'First series #009E73, backgrounds correct, both themes work perfectly' + design_excellence: + score: 13 + max: 20 + items: + - id: DE-01 + name: Aesthetic Sophistication + score: 4 + max: 8 + passed: false + comment: Professional with theme adaptation, lacks extraordinary sophistication + - id: DE-02 + name: Visual Refinement + score: 4 + max: 6 + passed: false + comment: Clean, minimal, could have more explicit spine/grid refinement + - id: DE-03 + name: Data Storytelling + score: 5 + max: 6 + passed: true + comment: Guides viewer through genome, thresholds provide context + spec_compliance: + score: 15 + max: 15 + items: + - id: SC-01 + name: Plot Type + score: 5 + max: 5 + passed: true + comment: XY scatter correctly implements manhattan plot + - id: SC-02 + name: Required Features + score: 4 + max: 4 + passed: true + comment: 'All features present: alternating colors, thresholds, significant + peaks' + - id: SC-03 + name: Data Mapping + score: 3 + max: 3 + passed: true + comment: X/Y axes correct, all data shown + - id: SC-04 + name: Title & Legend + score: 3 + max: 3 + passed: true + comment: Title format correct, legend labels match + data_quality: + score: 15 + max: 15 + items: + - id: DQ-01 + name: Feature Coverage + score: 6 + max: 6 + passed: true + comment: Full manhattan plot with all GWAS elements + - id: DQ-02 + name: Realistic Context + score: 5 + max: 5 + passed: true + comment: Realistic GWAS-like data, neutral domain context + - id: DQ-03 + name: Appropriate Scale + score: 4 + max: 4 + passed: true + comment: Realistic p-value range and standard thresholds + code_quality: + score: 10 + max: 10 + items: + - id: CQ-01 + name: KISS Structure + score: 3 + max: 3 + passed: true + comment: Straightforward, no unnecessary abstraction + - id: CQ-02 + name: Reproducibility + score: 2 + max: 2 + passed: true + comment: Has np.random.seed(42) + - id: CQ-03 + name: Clean Imports + score: 2 + max: 2 + passed: true + comment: Only necessary imports used + - id: CQ-04 + name: Code Elegance + score: 2 + max: 2 + passed: true + comment: Appropriate complexity, no fake functionality + - id: CQ-05 + name: Output & API + score: 1 + max: 1 + passed: true + comment: Correct output format and current API + library_mastery: + score: 6 + max: 10 + items: + - id: LM-01 + name: Idiomatic Usage + score: 4 + max: 5 + passed: true + comment: Good XY chart and Style usage, follows pygal conventions + - id: LM-02 + name: Distinctive Features + score: 2 + max: 5 + passed: false + comment: Limited showcase of distinctive pygal features + verdict: APPROVED impl_tags: dependencies: [] techniques: - manual-ticks - - layer-composition - hover-tooltips - - html-export patterns: - data-generation - iteration-over-groups dataprep: [] - styling: [] -review: - strengths: - - Excellent alternating blue/gray color scheme for chromosome distinction that is - colorblind-friendly - - Both genome-wide significance (p=5×10⁻⁸) and suggestive (p=1×10⁻⁵) threshold lines - are clearly visible - - Good use of pygal's tooltip system with informative labels showing chromosome, - position, and p-value - - Significant SNPs are properly highlighted in red as a separate series - - Clean chromosome labeling at midpoints with all 22 chromosomes visible - weaknesses: - - Contains a helper function (format_x_label) which violates the KISS principle - requiring flat script structure - - Data point markers are somewhat small for the 11,000+ point dataset; larger dots_size - would improve visibility - - Legend at bottom takes substantial vertical space; could be more compact + styling: + - publication-ready