diff --git a/plots/ternary-basic/implementations/python/pygal.py b/plots/ternary-basic/implementations/python/pygal.py index 8c3b87200c..da0e238739 100644 --- a/plots/ternary-basic/implementations/python/pygal.py +++ b/plots/ternary-basic/implementations/python/pygal.py @@ -1,10 +1,11 @@ -""" pyplots.ai +""" anyplot.ai ternary-basic: Basic Ternary Plot -Library: pygal 3.1.0 | Python 3.13.11 -Quality: 78/100 | Created: 2025-12-24 +Library: pygal 3.1.0 | Python 3.13.13 +Quality: 92/100 | Updated: 2026-05-06 """ import math +import os import cairosvg import numpy as np @@ -12,87 +13,79 @@ from pygal.style import Style +THEME = os.getenv("ANYPLOT_THEME", "light") +PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17" +ELEVATED_BG = "#FFFDF6" if THEME == "light" else "#242420" +INK = "#1A1A17" if THEME == "light" else "#F0EFE8" +INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0" +INK_MUTED = "#6B6A63" if THEME == "light" else "#A8A79F" +BRAND = "#009E73" + np.random.seed(42) -# Triangle height for equilateral triangle with base = 1 H = math.sqrt(3) / 2 -# Sample soil composition data (Sand%, Silt%, Clay%) - all sum to 100 -# Ternary coordinates: Top=Sand, Bottom-right=Silt, Bottom-left=Clay compositions = [ - (65, 25, 10), # Sandy Loam - (10, 45, 45), # Silty Clay - (30, 35, 35), # Clay Loam - (40, 40, 20), # Loam - (50, 10, 40), # Sandy Clay - (20, 65, 15), # Silt Loam - (90, 5, 5), # Pure Sand - (5, 90, 5), # Pure Silt - (5, 5, 90), # Pure Clay - (33, 34, 33), # Balanced - (45, 45, 10), # Sandy Silt - (55, 35, 10), # Silty Sand - (15, 20, 65), # Clay - (10, 15, 75), # Heavy Clay - (50, 30, 20), # Light Loam + (65, 25, 10), + (10, 45, 45), + (30, 35, 35), + (40, 40, 20), + (50, 10, 40), + (20, 65, 15), + (90, 5, 5), + (5, 90, 5), + (5, 5, 90), + (33, 34, 33), + (45, 45, 10), + (55, 35, 10), + (15, 20, 65), + (10, 15, 75), + (50, 30, 20), ] -# Convert ternary (sand, silt, clay) to cartesian (x, y) -# Formula: x = 0.5 * (2 * silt/100 + sand/100), y = H * sand/100 data_points = [(0.5 * (2 * s[1] + s[0]) / 100, H * s[0] / 100) for s in compositions] -# Triangle vertices -vertex_sand = (0.5, H) # Top - 100% Sand -vertex_silt = (1.0, 0.0) # Bottom-right - 100% Silt -vertex_clay = (0.0, 0.0) # Bottom-left - 100% Clay +vertex_sand = (0.5, H) +vertex_silt = (1.0, 0.0) +vertex_clay = (0.0, 0.0) -# Grid lines at 20%, 40%, 60%, 80% intervals -# Each line connects two edges of the triangle grid_lines = [] for pct in [0.2, 0.4, 0.6, 0.8]: - # Parallel to base (constant Sand %) - from left edge to right edge p1 = (0.5 * (2 * (1 - pct) + pct), H * pct) p2 = (0.5 * pct, H * pct) grid_lines.extend([p1, p2, (None, None)]) - # Parallel to left side (constant Silt %) - from base to right edge p1 = (0.5 * (2 * pct + (1 - pct)), H * (1 - pct)) p2 = (pct, 0.0) grid_lines.extend([p1, p2, (None, None)]) - # Parallel to right side (constant Clay %) - from base to left edge p1 = (0.5 * (1 - pct), H * (1 - pct)) p2 = (0.5 * (2 * (1 - pct)), 0.0) grid_lines.extend([p1, p2, (None, None)]) -# Tick marks along each edge at 20% intervals tick_marks = [] -tick_len = 0.03 # Length of tick marks +tick_len = 0.03 for pct in [0.2, 0.4, 0.6, 0.8]: - # Ticks on left edge (Clay-Sand): perpendicular outward x_left = 0.5 * pct y_left = H * pct tick_marks.extend([(x_left, y_left), (x_left - tick_len, y_left), (None, None)]) - # Ticks on right edge (Sand-Silt): perpendicular outward x_right = 0.5 * (2 - pct) y_right = H * pct tick_marks.extend([(x_right, y_right), (x_right + tick_len, y_right), (None, None)]) - # Ticks on base (Clay-Silt): perpendicular downward x_base = pct y_base = 0.0 tick_marks.extend([(x_base, y_base), (x_base, y_base - tick_len), (None, None)]) -# Custom style for 3600x3600 px canvas custom_style = Style( - background="white", - plot_background="white", - foreground="#333333", - foreground_strong="#333333", - foreground_subtle="#666666", - colors=("#333333", "#AAAAAA", "#306998", "#333333"), # Boundary, Grid, Data, Ticks + background=PAGE_BG, + plot_background=PAGE_BG, + foreground=INK, + foreground_strong=INK, + foreground_subtle=INK_MUTED, + colors=(INK, INK_MUTED, BRAND, INK), title_font_size=80, label_font_size=48, major_label_font_size=44, @@ -101,7 +94,6 @@ opacity=0.85, ) -# Create XY chart (square format for ternary plot) chart = pygal.XY( width=3600, height=3600, @@ -115,7 +107,7 @@ show_y_labels=False, x_title="", y_title="", - title="Soil Composition · ternary-basic · pygal · pyplots.ai", + title="Soil Composition · ternary-basic · pygal · anyplot.ai", dots_size=20, stroke=False, include_x_axis=False, @@ -126,26 +118,18 @@ margin_bottom=120, ) -# Triangle outline (no legend entry - structural element) chart.add( None, [vertex_clay, vertex_silt, vertex_sand, vertex_clay], stroke=True, show_dots=False, stroke_style={"width": 5} ) -# Grid lines at 20% intervals (no legend entry - structural element) chart.add(None, grid_lines, stroke=True, show_dots=False, stroke_style={"width": 2, "dasharray": "8,5"}) -# Data points (soil samples) - the only legend-worthy series chart.add("Soil Samples", data_points, stroke=False, dots_size=22) -# Tick marks along edges (no legend entry - structural element) chart.add(None, tick_marks, stroke=True, show_dots=False, stroke_style={"width": 3}) -# Render to SVG string first svg_content = chart.render().decode("utf-8") -# Calculate pixel positions for labels (inline conversion - KISS principle) -# The chart has xrange=(-0.15, 1.15) = 1.30 range and yrange=(-0.20, 1.05) = 1.25 range -# Approximate: plot area starts after margin and title plot_x_start = 150 plot_x_end = 3450 plot_y_start = 250 @@ -153,7 +137,6 @@ x_range = 1.30 y_range = 1.25 -# Vertex label positions (offset from triangle vertices) sand_px = plot_x_start + (0.5 + 0.15) / x_range * (plot_x_end - plot_x_start) sand_py = plot_y_start + (1.05 - (H + 0.06)) / y_range * (plot_y_end - plot_y_start) silt_px = plot_x_start + (1.07 + 0.15) / x_range * (plot_x_end - plot_x_start) @@ -161,48 +144,40 @@ clay_px = plot_x_start + (-0.07 + 0.15) / x_range * (plot_x_end - plot_x_start) clay_py = plot_y_start + (1.05 - (-0.03)) / y_range * (plot_y_end - plot_y_start) -# Build SVG text elements for vertex labels vertex_labels_svg = f""" - SAND - SILT - CLAY + SAND + SILT + CLAY """ -# Percentage labels along each edge at 20%, 40%, 60%, 80% pct_labels_svg = "" pct_font_size = 36 for pct in [20, 40, 60, 80]: frac = pct / 100.0 - # Left edge (Clay-Sand): Sand % increases going up, Clay % decreases left_x = 0.5 * frac left_y = H * frac left_px = plot_x_start + (left_x - 0.06 + 0.15) / x_range * (plot_x_end - plot_x_start) left_py = plot_y_start + (1.05 - left_y) / y_range * (plot_y_end - plot_y_start) - pct_labels_svg += f' {pct}\n' + pct_labels_svg += f' {pct}\n' - # Right edge (Sand-Silt): Sand % increases going up, Silt % increases going down-right right_x = 0.5 * (2 - frac) right_y = H * frac right_px = plot_x_start + (right_x + 0.04 + 0.15) / x_range * (plot_x_end - plot_x_start) right_py = plot_y_start + (1.05 - right_y) / y_range * (plot_y_end - plot_y_start) - pct_labels_svg += f' {pct}\n' + pct_labels_svg += f' {pct}\n' - # Bottom edge (Clay-Silt): Clay % decreases left-to-right, Silt % increases base_x = frac base_y = -0.05 base_px = plot_x_start + (base_x + 0.15) / x_range * (plot_x_end - plot_x_start) base_py = plot_y_start + (1.05 - base_y) / y_range * (plot_y_end - plot_y_start) - pct_labels_svg += f' {pct}\n' + pct_labels_svg += f' {pct}\n' -# Insert all labels before the closing tag all_labels_svg = vertex_labels_svg + pct_labels_svg svg_content = svg_content.replace("", all_labels_svg + "") -# Save as SVG for HTML output -with open("plot.html", "w") as f: +with open(f"plot-{THEME}.html", "w") as f: f.write(svg_content) -# Convert to PNG -cairosvg.svg2png(bytestring=svg_content.encode("utf-8"), write_to="plot.png") +cairosvg.svg2png(bytestring=svg_content.encode("utf-8"), write_to=f"plot-{THEME}.png") diff --git a/plots/ternary-basic/metadata/python/pygal.yaml b/plots/ternary-basic/metadata/python/pygal.yaml index 3cbe96e916..815bfbbc11 100644 --- a/plots/ternary-basic/metadata/python/pygal.yaml +++ b/plots/ternary-basic/metadata/python/pygal.yaml @@ -1,40 +1,193 @@ library: pygal +language: python specification_id: ternary-basic created: '2025-12-24T09:54:54Z' -updated: '2025-12-24T10:23:09Z' -generated_by: claude-opus-4-5-20251101 -workflow_run: 20483448048 -issue: 0 -python_version: 3.13.11 +updated: '2026-05-06T03:54:39Z' +generated_by: claude-haiku +workflow_run: 25414898498 +issue: 1001 +python_version: 3.13.13 library_version: 3.1.0 -preview_url: https://storage.googleapis.com/anyplot-images/plots/ternary-basic/pygal/plot.png -preview_html: https://storage.googleapis.com/anyplot-images/plots/ternary-basic/pygal/plot.html -quality_score: 78 +preview_url_light: https://storage.googleapis.com/anyplot-images/plots/ternary-basic/python/pygal/plot-light.png +preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/ternary-basic/python/pygal/plot-dark.png +preview_html_light: https://storage.googleapis.com/anyplot-images/plots/ternary-basic/python/pygal/plot-light.html +preview_html_dark: https://storage.googleapis.com/anyplot-images/plots/ternary-basic/python/pygal/plot-dark.html +quality_score: 92 +review: + strengths: + - 'Palette compliance fixed: data points now correctly render as #009E73 brand green' + - Perfect theme adaptation with proper chrome switching + - Clean, professional layout with no text overlaps + - Complete specification implementation with all ternary features + - Strong code quality with proper reproducibility and structure + weaknesses: [] + image_description: |- + Light render (plot-light.png): + Background: Warm off-white #FAF8F1 - correct + Chrome: Title in dark text #1A1A17, vertex labels bold and clear, percentage labels in muted ink #6B6A63, grid lines subtle dashed pattern + Data: 15 data points rendered as brand green circles #009E73, clearly visible and well-positioned + Legibility verdict: PASS + + Dark render (plot-dark.png): + Background: Warm near-black #1A1A17 - correct + Chrome: Title in light text #F0EFE8, vertex labels visible in light text, percentage labels in light muted #A8A79F, grid lines subtle + Data: 15 data points remain as brand green #009E73 - identical to light render (only chrome flipped) + Legibility verdict: PASS + criteria_checklist: + visual_quality: + score: 30 + max: 30 + items: + - id: VQ-01 + name: Text Legibility + score: 8 + max: 8 + passed: true + - id: VQ-02 + name: No Overlap + score: 6 + max: 6 + passed: true + - id: VQ-03 + name: Element Visibility + score: 6 + max: 6 + passed: true + - id: VQ-04 + name: Color Accessibility + score: 2 + max: 2 + passed: true + - id: VQ-05 + name: Layout & Canvas + score: 4 + max: 4 + passed: true + - id: VQ-06 + name: Axis Labels & Title + score: 2 + max: 2 + passed: true + - id: VQ-07 + name: Palette Compliance + score: 2 + max: 2 + passed: true + design_excellence: + score: 14 + max: 20 + items: + - id: DE-01 + name: Aesthetic Sophistication + score: 6 + max: 8 + passed: true + - id: DE-02 + name: Visual Refinement + score: 4 + max: 6 + passed: true + - id: DE-03 + name: Data Storytelling + score: 4 + max: 6 + passed: true + spec_compliance: + score: 15 + max: 15 + items: + - id: SC-01 + name: Plot Type + score: 5 + max: 5 + passed: true + - id: SC-02 + name: Required Features + score: 4 + max: 4 + passed: true + - id: SC-03 + name: Data Mapping + score: 3 + max: 3 + passed: true + - id: SC-04 + name: Title & Legend + score: 3 + max: 3 + passed: true + data_quality: + score: 15 + max: 15 + items: + - id: DQ-01 + name: Feature Coverage + score: 6 + max: 6 + passed: true + - id: DQ-02 + name: Realistic Context + score: 5 + max: 5 + passed: true + - id: DQ-03 + name: Appropriate Scale + score: 4 + max: 4 + passed: true + code_quality: + score: 10 + max: 10 + items: + - id: CQ-01 + name: KISS Structure + score: 3 + max: 3 + passed: true + - id: CQ-02 + name: Reproducibility + score: 2 + max: 2 + passed: true + - id: CQ-03 + name: Clean Imports + score: 2 + max: 2 + passed: true + - id: CQ-04 + name: Code Elegance + score: 2 + max: 2 + passed: true + - id: CQ-05 + name: Output & API + score: 1 + max: 1 + passed: true + library_mastery: + score: 8 + max: 10 + items: + - id: LM-01 + name: Idiomatic Usage + score: 5 + max: 5 + passed: true + - id: LM-02 + name: Distinctive Features + score: 3 + max: 5 + passed: true + verdict: APPROVED impl_tags: dependencies: - cairosvg techniques: - - manual-ticks - annotations - html-export patterns: + - data-generation - matrix-construction - - iteration-over-groups dataprep: [] styling: - - grid-styling -review: - strengths: - - Creative implementation of ternary plot using pygal XY chart type since pygal - lacks native ternary support - - Excellent use of SVG manipulation to add vertex and percentage labels that pygal - cannot natively produce - - Realistic soil composition data with meaningful variation across the ternary space - - Proper ternary coordinate transformation from compositional data to Cartesian - coordinates - - Clean grid lines at 20% intervals with appropriate styling - - Correct title format following the spec-id · library · pyplots.ai convention - weaknesses: - - Legend shows only Soil Samples which adds little value for a single-series plot - - Some whitespace below the plot area could be better utilized - - Percentage labels along edges are relatively small compared to vertex labels + - custom-colormap