diff --git a/plots/scatter-marginal/implementations/python/pygal.py b/plots/scatter-marginal/implementations/python/pygal.py index dad4585c46..2f3100ff67 100644 --- a/plots/scatter-marginal/implementations/python/pygal.py +++ b/plots/scatter-marginal/implementations/python/pygal.py @@ -1,10 +1,11 @@ -""" pyplots.ai +""" anyplot.ai scatter-marginal: Scatter Plot with Marginal Distributions -Library: pygal 3.1.0 | Python 3.13.11 -Quality: 88/100 | Created: 2025-12-26 +Library: pygal 3.1.0 | Python 3.13.13 +Quality: 90/100 | Updated: 2026-05-09 """ import io +import os import numpy as np import pygal @@ -12,6 +13,18 @@ from pygal.style import Style +THEME = os.getenv("ANYPLOT_THEME", "light") + +# Theme-adaptive colors from default-style-guide.md +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 palette +BRAND = "#009E73" # First categorical series +SECONDARY = "#D55E00" # For marginals if colored + # Data - correlated bivariate data with realistic measurement context np.random.seed(42) n_points = 150 @@ -22,7 +35,6 @@ correlation = np.corrcoef(x, y)[0, 1] # Calculate histogram data for marginals -# Use same number of bins and explicit ranges for better alignment n_bins = 10 x_min, x_max = np.floor(x.min() / 5) * 5, np.ceil(x.max() / 5) * 5 y_min, y_max = np.floor(y.min() / 5) * 5, np.ceil(y.max() / 5) * 5 @@ -30,60 +42,53 @@ x_hist, x_edges = np.histogram(x, bins=n_bins, range=(x_min, x_max)) y_hist, y_edges = np.histogram(y, bins=n_bins, range=(y_min, y_max)) -# Dimensions for layout - optimized spacing +# Dimensions for layout total_width = 4800 total_height = 2700 -margin_plot_size = 450 # Slightly smaller marginals +margin_plot_size = 450 title_height = 100 gap = 15 -corner_size = margin_plot_size # Size for corner annotation -# Calculate main scatter dimensions scatter_width = total_width - margin_plot_size - gap * 3 scatter_height = total_height - margin_plot_size - title_height - gap * 3 -# Shared margins for alignment left_margin = 100 bottom_margin = 80 top_margin = 20 right_margin = 20 -# Custom style for main scatter +# Custom style for main scatter - theme-adaptive scatter_style = Style( - background="#ffffff", - plot_background="#fafafa", - foreground="#333333", - foreground_strong="#333333", - foreground_subtle="#666666", - colors=("#306998",), + background=PAGE_BG, + plot_background=PAGE_BG, + foreground=INK, + foreground_strong=INK, + foreground_subtle=INK_MUTED, + colors=(BRAND,), # Okabe-Ito first series title_font_size=48, label_font_size=36, major_label_font_size=32, legend_font_size=32, - tooltip_font_size=24, opacity=0.65, opacity_hover=0.9, - guide_stroke_color="#e0e0e0", - major_guide_stroke_color="#cccccc", ) -# Custom style for marginal histograms - subtle color to not distract from main scatter +# Custom style for marginal histograms - theme-adaptive, subtle color marginal_style = Style( - background="#ffffff", - plot_background="#f8f8f8", - foreground="#333333", - foreground_strong="#333333", - foreground_subtle="#666666", - colors=("#a8c5db",), # Subtle, lighter blue for marginals + background=PAGE_BG, + plot_background=PAGE_BG, + foreground=INK, + foreground_strong=INK, + foreground_subtle=INK_MUTED, + colors=(INK_SOFT,), # Subtle gray for marginals title_font_size=32, label_font_size=32, major_label_font_size=30, legend_font_size=28, - opacity=0.6, # More transparency for subtle appearance - guide_stroke_color="#e0e0e0", + opacity=0.6, ) -# Create main scatter plot with explicit axis ranges matching marginal histograms +# Create main scatter plot scatter = pygal.XY( width=scatter_width, height=scatter_height, @@ -102,22 +107,20 @@ margin_right=right_margin, margin_bottom=bottom_margin, margin_left=left_margin, - range=(y_min - 5, y_max + 5), # Y range with slight padding - xrange=(x_min - 5, x_max + 5), # X range with slight padding + range=(y_min - 5, y_max + 5), + xrange=(x_min - 5, x_max + 5), ) -# Add scatter data scatter_points = [(float(xi), float(yi)) for xi, yi in zip(x, y, strict=True)] scatter.add("Data", scatter_points) # Create top marginal histogram (X distribution) -# Use bar chart with x_labels matching scatter X range bins x_margin = pygal.Bar( width=scatter_width, height=margin_plot_size, style=marginal_style, show_legend=False, - show_x_labels=False, # Hide to avoid clutter + show_x_labels=False, show_y_labels=True, show_y_guides=True, show_x_guides=False, @@ -126,12 +129,11 @@ margin_bottom=20, margin_left=left_margin, explicit_size=True, - spacing=2, # Tighter spacing for better visual continuity + spacing=2, ) x_margin.add("X Distribution", [float(h) for h in x_hist]) -# Create right marginal histogram (Y distribution) - horizontal bars -# Use same margin settings as scatter for better alignment +# Create right marginal histogram (Y distribution) y_margin = pygal.HorizontalBar( width=margin_plot_size, height=scatter_height, @@ -143,12 +145,11 @@ show_x_guides=False, margin_top=top_margin, margin_right=30, - margin_bottom=bottom_margin, # Match scatter bottom margin + margin_bottom=bottom_margin, margin_left=10, explicit_size=True, - spacing=2, # Tighter spacing matching X marginal + spacing=2, ) -# Reverse order to match scatter Y axis orientation (pygal HorizontalBar goes top-to-bottom) y_margin.add("Y Distribution", [float(h) for h in y_hist[::-1]]) # Render each chart to PNG in memory @@ -161,10 +162,10 @@ x_margin_img = Image.open(io.BytesIO(x_margin_png)) y_margin_img = Image.open(io.BytesIO(y_margin_png)) -# Create final composite image -final_img = Image.new("RGB", (total_width, total_height), "white") +# Create final composite image with theme-adaptive background +final_img = Image.new("RGB", (total_width, total_height), PAGE_BG) -# Calculate positions - aligned for better visual coherence +# Calculate positions scatter_x = gap scatter_y = title_height + margin_plot_size + gap x_margin_x = gap @@ -179,7 +180,7 @@ # Add title and corner annotation draw = ImageDraw.Draw(final_img) -title_text = "scatter-marginal · pygal · pyplots.ai" +title_text = "scatter-marginal · pygal · anyplot.ai" try: title_font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 60) stats_font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 36) @@ -194,33 +195,35 @@ text_width = bbox[2] - bbox[0] text_x = (total_width - text_width) // 2 text_y = 30 -draw.text((text_x, text_y), title_text, fill="#333333", font=title_font) +draw.text((text_x, text_y), title_text, fill=INK, font=title_font) # Add statistics in the corner space (top-right empty area) -# This corner is at: x = right of top marginal, y = below title and above right marginal -corner_x = y_margin_x + 30 # Right side where y_margin is -corner_y = title_height + 30 # Just below title area +corner_x = y_margin_x + 30 +corner_y = title_height + 30 corner_width = margin_plot_size - 60 corner_height = margin_plot_size - 80 -# Draw subtle background for stats box +# Draw subtle background for stats box with theme-adaptive colors +elevated_bg = "#FFFDF6" if THEME == "light" else "#242420" +box_border = INK_MUTED + stats_box = [(corner_x, corner_y), (corner_x + corner_width, corner_y + corner_height)] -draw.rounded_rectangle(stats_box, radius=15, fill="#f5f5f5", outline="#c0c0c0", width=2) +draw.rounded_rectangle(stats_box, radius=15, fill=elevated_bg, outline=box_border, width=2) -# Add statistics text - centered in box +# Add statistics text stats_title = "Summary" -draw.text((corner_x + 35, corner_y + 25), stats_title, fill="#333333", font=stats_font_bold) +draw.text((corner_x + 35, corner_y + 25), stats_title, fill=INK, font=stats_font_bold) stats_lines = [f"n = {n_points}", f"r = {correlation:.3f}", f"A̅ = {np.mean(x):.1f}", f"B̅ = {np.mean(y):.1f}"] line_y = corner_y + 85 for line in stats_lines: - draw.text((corner_x + 35, line_y), line, fill="#555555", font=stats_font) + draw.text((corner_x + 35, line_y), line, fill=INK_SOFT, font=stats_font) line_y += 50 -# Save final image -final_img.save("plot.png", "PNG") +# Save final image and HTML +final_img.save(f"plot-{THEME}.png", "PNG") # Also save the scatter SVG as HTML for interactivity scatter_svg_full = scatter.render().decode("utf-8") -with open("plot.html", "w", encoding="utf-8") as f: +with open(f"plot-{THEME}.html", "w", encoding="utf-8") as f: f.write(scatter_svg_full) diff --git a/plots/scatter-marginal/metadata/python/pygal.yaml b/plots/scatter-marginal/metadata/python/pygal.yaml index b5fac09763..77ee147780 100644 --- a/plots/scatter-marginal/metadata/python/pygal.yaml +++ b/plots/scatter-marginal/metadata/python/pygal.yaml @@ -1,41 +1,254 @@ library: pygal +language: python specification_id: scatter-marginal created: '2025-12-26T15:02:10Z' -updated: '2025-12-26T15:22:04Z' -generated_by: claude-opus-4-5-20251101 -workflow_run: 20524419004 -issue: 0 -python_version: 3.13.11 +updated: '2026-05-09T05:32:14Z' +generated_by: claude-haiku +workflow_run: 25592866414 +issue: 2005 +python_version: 3.13.13 library_version: 3.1.0 -preview_url: https://storage.googleapis.com/anyplot-images/plots/scatter-marginal/pygal/plot.png -preview_html: https://storage.googleapis.com/anyplot-images/plots/scatter-marginal/pygal/plot.html -quality_score: 88 +preview_url_light: https://storage.googleapis.com/anyplot-images/plots/scatter-marginal/python/pygal/plot-light.png +preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/scatter-marginal/python/pygal/plot-dark.png +preview_html_light: https://storage.googleapis.com/anyplot-images/plots/scatter-marginal/python/pygal/plot-light.html +preview_html_dark: https://storage.googleapis.com/anyplot-images/plots/scatter-marginal/python/pygal/plot-dark.html +quality_score: 90 +review: + strengths: + - Perfect visual quality with all text readable in both light and dark themes + - 'Flawless spec compliance: scatter-marginal correctly implemented with proper + axis alignment and marginal distribution placement' + - 'Excellent theme adaptation: data colors (#009E73) identical across themes, all + chrome elements properly theme-adaptive' + - 'Strong code quality: clean, reproducible (seed=42), proper theme-token usage + throughout' + - Well-designed composite layout with generous whitespace and balanced margins for + 4800×2700 canvas + - Summary statistics box adds valuable context (correlation, means, n) without visual + clutter + - 'Perfect data quality: realistic bivariate correlation scenario with diverse distributions + visible in both margins' + weaknesses: + - 'Design sophistication could be elevated: color palette is minimal (only brand + green + neutral grays), limited visual flourishes or distinctive design elements' + - 'Library mastery: PIL image composition is somewhat manual; could explore pygal''s + native multi-chart capabilities more idiomatically' + - 'Visual refinement could push further: spines left as defaults, grid styling is + subtle but could be more intentionally designed, limited visual hierarchy or emphasis + techniques' + image_description: |- + Light render (plot-light.png): + Background: Warm off-white around #FAF8F1, matches spec requirement perfectly + Chrome: Title "scatter-marginal · pygal · anyplot.ai" is centered and clear in dark INK color. Axis labels "Measurement A (units)" and "Measurement B (units)" are positioned correctly with good spacing. All tick labels are dark and readable. Summary box has dark text on slightly elevated background. + Data: Scatter points are teal (#009E73), marginal histograms are subtle gray. All markers clearly visible with 150 points at opacity 0.65. No overlapping or hidden elements. + Legibility verdict: PASS - all text is crisp and readable against the light off-white surface. Perfect contrast and font sizing throughout. + + Dark render (plot-dark.png): + Background: Warm near-black around #1A1A17, matches spec requirement perfectly + Chrome: Title is light-colored (#F0EFE8) and clearly visible. Axis labels and tick labels are light gray (#B8B7B0), readable with good contrast. Summary box has appropriate elevated background (#242420) with light text. No dark-on-dark issues. + Data: Scatter data points remain teal (#009E73) - identical colors to light render, confirming theme-invariant data. Marginal bars use theme-adapted gray. All elements are distinguishable and visible. + Legibility verdict: PASS - all text is readable on the dark surface. Chrome colors properly adapt to dark theme. No contrast failures or dark-on-dark problems. Both renders are equally legible. + criteria_checklist: + visual_quality: + score: 30 + max: 30 + items: + - id: VQ-01 + name: Text Legibility + score: 8 + max: 8 + passed: true + comment: All font sizes explicitly set in Style object and ImageDraw. Perfect + readability in both themes. + - id: VQ-02 + name: No Overlap + score: 6 + max: 6 + passed: true + comment: Excellent spacing throughout. No overlapping text elements. Axis + labels, ticks, and summary box clearly separated. + - id: VQ-03 + name: Element Visibility + score: 6 + max: 6 + passed: true + comment: Scatter markers perfectly adapted to 150 points (size=10, opacity=0.65). + Marginal bars clearly visible. No overlapping. + - id: VQ-04 + name: Color Accessibility + score: 2 + max: 2 + passed: true + comment: Okabe-Ito first series (#009E73) is CVD-safe. Marginal gray provides + good luminance contrast. + - id: VQ-05 + name: Layout & Canvas + score: 4 + max: 4 + passed: true + comment: 4800×2700 landscape optimal. Composite fills 60-70% of canvas. Balanced + margins, nothing cut off. + - id: VQ-06 + name: Axis Labels & Title + score: 2 + max: 2 + passed: true + comment: Descriptive labels with units (Measurement A/B). Title format correct. + - id: VQ-07 + name: Palette Compliance + score: 2 + max: 2 + passed: true + comment: 'First series #009E73 ✓. Backgrounds #FAF8F1/#1A1A17 ✓. Theme-adaptive + tokens throughout. Data identical across themes, chrome flips correctly. + Both renders theme-correct.' + design_excellence: + score: 13 + max: 20 + items: + - id: DE-01 + name: Aesthetic Sophistication + score: 5 + max: 8 + passed: false + comment: Clean design with custom Style objects and theme adaptation. Summary + box adds polish. Color palette minimal (brand + grays). Professional but + not exceptional. + - id: DE-02 + name: Visual Refinement + score: 4 + max: 6 + passed: false + comment: Grid subtle via foreground_subtle. Generous whitespace. Summary box + has rounded corners and elevated background. Could push further with spine + removal. + - id: DE-03 + name: Data Storytelling + score: 4 + max: 6 + passed: false + comment: Shows correlation AND distributions clearly. Summary statistics add + context. Visual hierarchy guides viewer. Good storytelling through statistics + and layout, but could leverage more visual emphasis. + spec_compliance: + score: 15 + max: 15 + items: + - id: SC-01 + name: Plot Type + score: 5 + max: 5 + passed: true + comment: 'Correct scatter-marginal: main XY scatter lower-left, top marginal + (X), right marginal (Y). All subtypes present.' + - id: SC-02 + name: Required Features + score: 4 + max: 4 + passed: true + comment: 'All features present: bivariate scatter, marginal histograms, proper + alignment, axes show all data.' + - id: SC-03 + name: Data Mapping + score: 3 + max: 3 + passed: true + comment: X → Measurement A (horizontal), Y → Measurement B (vertical). Axes + ranges appropriate. Marginals aligned. + - id: SC-04 + name: Title & Legend + score: 3 + max: 3 + passed: true + comment: Title format correct. No legend (single series, appropriate). Axis + labels descriptive with units. + data_quality: + score: 15 + max: 15 + items: + - id: DQ-01 + name: Feature Coverage + score: 6 + max: 6 + passed: true + comment: 'Shows all features: positive correlation, realistic X distribution, + diverse Y distribution. Outliers and density patterns visible.' + - id: DQ-02 + name: Realistic Context + score: 5 + max: 5 + passed: true + comment: Neutral, plausible measurement scenario. Correlated bivariate is + realistic. No controversial elements. + - id: DQ-03 + name: Appropriate Scale + score: 4 + max: 4 + passed: true + comment: Ranges sensible (x~10-90, y~10-110). Correlation r=0.556 realistic. + Sample n=150 appropriate. Factually consistent. + code_quality: + score: 10 + max: 10 + items: + - id: CQ-01 + name: KISS Structure + score: 3 + max: 3 + passed: true + comment: 'Linear flow: config → data → charts → compose → save. No unnecessary + functions/classes.' + - id: CQ-02 + name: Reproducibility + score: 2 + max: 2 + passed: true + comment: np.random.seed(42) set. All data deterministic. + - id: CQ-03 + name: Clean Imports + score: 2 + max: 2 + passed: true + comment: 'All imports used: io, os, numpy, pygal, PIL. No extraneous.' + - id: CQ-04 + name: Code Elegance + score: 2 + max: 2 + passed: true + comment: Clean, Pythonic, appropriate complexity. No fake functionality or + over-engineering. + - id: CQ-05 + name: Output & API + score: 1 + max: 1 + passed: true + comment: Saves plot-{THEME}.png and .html. Current pygal API. + library_mastery: + score: 7 + max: 10 + items: + - id: LM-01 + name: Idiomatic Usage + score: 4 + max: 5 + passed: false + comment: Correct high-level API usage (XY, Bar, HorizontalBar). Proper Style + configuration. Good parameters. PIL composition somewhat outside pygal workflow. + - id: LM-02 + name: Distinctive Features + score: 3 + max: 5 + passed: false + comment: Creative PIL image composition for layout. Custom Style objects with + theme adaptation. Could leverage pygal's unique strengths more idiomatically. + verdict: APPROVED impl_tags: dependencies: - pillow - techniques: - - subplots - - annotations + techniques: [] patterns: - data-generation - - matrix-construction dataprep: - binning styling: - alpha-blending -review: - strengths: - - Creative solution using pygal chart compositing with PIL to achieve marginal distributions - (pygal does not natively support this) - - Clean visual design with subtle colors for marginals that do not distract from - main scatter - - Helpful statistics summary box showing sample size, correlation, and means - - Good use of transparency (alpha 0.65) for scatter points to show density - - Proper title format and professional appearance - weaknesses: - - Marginal histogram bins do not perfectly align with scatter plot axis ranges (visual - alignment gap between charts) - - Axis labels in the final image show X Value/Y Value rather than the intended Measurement - A/B (units) - - Right marginal histogram appears slightly disconnected from the main scatter plot - area