diff --git a/plots/rose-basic/implementations/python/plotnine.py b/plots/rose-basic/implementations/python/plotnine.py index 839b4d7ce2..1b2e266c4d 100644 --- a/plots/rose-basic/implementations/python/plotnine.py +++ b/plots/rose-basic/implementations/python/plotnine.py @@ -1,16 +1,27 @@ -""" pyplots.ai +""" anyplot.ai rose-basic: Basic Rose Chart -Library: plotnine 0.15.2 | Python 3.13.11 -Quality: 91/100 | Created: 2025-12-23 +Library: plotnine 0.15.3 | Python 3.13.13 +Quality: 85/100 | Updated: 2026-04-30 """ import math +import os +import sys + + +# Remove this file's own directory from sys.path so that "plotnine" resolves +# to the installed library rather than this file (Python 3.13 naming-collision fix). +_script_dir = os.path.dirname(os.path.abspath(__file__)) +sys.path = [p for p in sys.path if os.path.realpath(p) != os.path.realpath(_script_dir)] import numpy as np import pandas as pd +from matplotlib import colormaps +from matplotlib.colors import Normalize, to_hex from plotnine import ( aes, element_blank, + element_rect, element_text, geom_line, geom_polygon, @@ -24,133 +35,97 @@ ) +# Theme tokens +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" + # Data - Monthly rainfall (mm) for a temperate climate months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] rainfall = [78, 62, 55, 48, 52, 68, 82, 85, 72, 88, 95, 82] n = len(months) +# Viridis colors per month based on rainfall value (continuous colormap for value-encoded data) +_norm = Normalize(vmin=min(rainfall), vmax=max(rainfall)) +_cmap = colormaps["viridis"] +month_colors = {month: to_hex(_cmap(_norm(val))) for month, val in zip(months, rainfall, strict=True)} + # Create wedge polygons for each month -# Each wedge is a triangle from center to the arc +n_arc_points = 30 wedge_rows = [] -n_arc_points = 30 # Points along the arc for smooth edges - for i, (month, value) in enumerate(zip(months, rainfall, strict=True)): - # Starting angle (12 o'clock = pi/2, going clockwise) - # Adjust so January is at top (index 0 starts at pi/2) - # Use negative direction for clockwise motion start_angle = math.pi / 2 - (i * 2 * math.pi / n) end_angle = math.pi / 2 - ((i + 1) * 2 * math.pi / n) - - # Small gap between wedges gap = 0.02 start_angle += gap end_angle -= gap - - # Center point wedge_rows.append({"x": 0, "y": 0, "month": month, "order": 0}) - - # Arc points - arc_angles = np.linspace(start_angle, end_angle, n_arc_points) - for j, angle in enumerate(arc_angles): - x = value * math.cos(angle) - y = value * math.sin(angle) - wedge_rows.append({"x": x, "y": y, "month": month, "order": j + 1}) - - # Close back to center + for j, angle in enumerate(np.linspace(start_angle, end_angle, n_arc_points)): + wedge_rows.append({"x": value * math.cos(angle), "y": value * math.sin(angle), "month": month, "order": j + 1}) wedge_rows.append({"x": 0, "y": 0, "month": month, "order": n_arc_points + 1}) df = pd.DataFrame(wedge_rows) - -# Preserve month order df["month"] = pd.Categorical(df["month"], categories=months, ordered=True) -# Colors - gradient based on rainfall values -# Using Python Blue (#306998) as base with varying intensity -base_blue = np.array([48, 105, 152]) # RGB for #306998 -python_yellow = np.array([255, 212, 59]) # RGB for #FFD43B - -# Normalize rainfall for color mapping -min_val, max_val = min(rainfall), max(rainfall) -colors = [] -for value in rainfall: - t = (value - min_val) / (max_val - min_val) # 0 to 1 - # Interpolate from blue to yellow - rgb = (1 - t) * base_blue + t * python_yellow - colors.append(f"#{int(rgb[0]):02x}{int(rgb[1]):02x}{int(rgb[2]):02x}") - -color_dict = dict(zip(months, colors, strict=True)) - -# Create radial gridlines (circles at 20, 40, 60, 80, 100) +# Radial gridlines (circles) grid_rows = [] grid_angles = np.linspace(0, 2 * math.pi, 101) for radius in [20, 40, 60, 80, 100]: for angle in grid_angles: grid_rows.append({"x": radius * math.cos(angle), "y": radius * math.sin(angle), "radius": radius}) - grid_df = pd.DataFrame(grid_rows) -# Create spoke lines (one for each month boundary) +# Spoke lines from center to edge spoke_rows = [] for i in range(n): angle = math.pi / 2 - (i * 2 * math.pi / n) spoke_rows.append({"x": 0, "y": 0, "spoke_id": i}) spoke_rows.append({"x": 105 * math.cos(angle), "y": 105 * math.sin(angle), "spoke_id": i}) - spoke_df = pd.DataFrame(spoke_rows) -# Create month labels positioned outside the chart +# Month labels positioned outside the chart label_rows = [] for i, month in enumerate(months): - # Center angle of each month's wedge center_angle = math.pi / 2 - ((i + 0.5) * 2 * math.pi / n) - label_rows.append({"label": month, "x": 115 * math.cos(center_angle), "y": 115 * math.sin(center_angle)}) - + label_rows.append({"label": month, "x": 120 * math.cos(center_angle), "y": 120 * math.sin(center_angle)}) label_df = pd.DataFrame(label_rows) -# Create value labels on gridlines -value_label_rows = [] -for radius in [20, 40, 60, 80, 100]: - value_label_rows.append({"label": str(radius), "x": 5, "y": radius + 3}) - +# Value labels positioned along the top-right spoke +value_label_rows = [{"label": str(r), "x": 6, "y": r + 4} for r in [20, 40, 60, 80, 100]] value_label_df = pd.DataFrame(value_label_rows) # Plot plot = ( ggplot() - # Gridlines (circles) + geom_line( - aes(x="x", y="y", group="radius"), data=grid_df, color="#CCCCCC", size=0.5, alpha=0.6, linetype="dashed" + aes(x="x", y="y", group="radius"), data=grid_df, color=INK_SOFT, size=0.4, alpha=0.15, linetype="dashed" ) - # Spoke lines - + geom_line(aes(x="x", y="y", group="spoke_id"), data=spoke_df, color="#DDDDDD", size=0.3, alpha=0.5) - # Rose wedges - + geom_polygon(aes(x="x", y="y", fill="month", group="month"), data=df, color="#2C3E50", size=0.3, alpha=0.85) - # Month labels - + geom_text(aes(x="x", y="y", label="label"), data=label_df, size=14, fontweight="bold", color="#333333") - # Value labels - + geom_text(aes(x="x", y="y", label="label"), data=value_label_df, size=10, color="#666666") - # Colors - + scale_fill_manual(values=color_dict) - # Axis scaling - + scale_x_continuous(limits=(-135, 135)) - + scale_y_continuous(limits=(-135, 135)) - # Labels and title - + labs(title="Monthly Rainfall (mm) · rose-basic · plotnine · pyplots.ai") - # Theme for clean rose chart appearance + + geom_line(aes(x="x", y="y", group="spoke_id"), data=spoke_df, color=INK_SOFT, size=0.3, alpha=0.12) + + geom_polygon(aes(x="x", y="y", fill="month", group="month"), data=df, color=PAGE_BG, size=0.3, alpha=0.88) + + geom_text(aes(x="x", y="y", label="label"), data=label_df, size=14, fontweight="bold", color=INK) + + geom_text(aes(x="x", y="y", label="label"), data=value_label_df, size=10, color=INK_MUTED) + + scale_fill_manual(values=month_colors) + + scale_x_continuous(limits=(-142, 142)) + + scale_y_continuous(limits=(-142, 142)) + + labs(title="Monthly Rainfall (mm) · rose-basic · plotnine · anyplot.ai") + theme( figure_size=(12, 12), - plot_title=element_text(size=22, ha="center"), + plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG), + panel_background=element_rect(fill=PAGE_BG), + plot_title=element_text(size=22, ha="center", color=INK), axis_title=element_blank(), axis_text=element_blank(), axis_ticks=element_blank(), axis_line=element_blank(), panel_grid_major=element_blank(), panel_grid_minor=element_blank(), - panel_background=element_blank(), + panel_border=element_blank(), legend_position="none", ) ) # Save -plot.save("plot.png", dpi=300) +plot.save(f"plot-{THEME}.png", dpi=300) diff --git a/plots/rose-basic/metadata/python/plotnine.yaml b/plots/rose-basic/metadata/python/plotnine.yaml index 21ddd5aedc..36077d04cf 100644 --- a/plots/rose-basic/metadata/python/plotnine.yaml +++ b/plots/rose-basic/metadata/python/plotnine.yaml @@ -1,171 +1,177 @@ library: plotnine +language: python specification_id: rose-basic created: '2025-12-23T19:59:11Z' -updated: '2025-12-23T20:02:22Z' -generated_by: claude-opus-4-5-20251101 -workflow_run: 20470198156 -issue: 0 -python_version: 3.13.11 -library_version: 0.15.2 -preview_url: https://storage.googleapis.com/anyplot-images/plots/rose-basic/plotnine/plot.png -preview_html: null -quality_score: 91 -impl_tags: - dependencies: [] - techniques: - - layer-composition - patterns: - - data-generation - - iteration-over-groups - - matrix-construction - dataprep: [] - styling: [] +updated: '2026-04-30T07:32:55Z' +generated_by: claude-sonnet +workflow_run: 25151963204 +issue: 1003 +python_version: 3.13.13 +library_version: 0.15.3 +preview_url_light: https://storage.googleapis.com/anyplot-images/plots/rose-basic/python/plotnine/plot-light.png +preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/rose-basic/python/plotnine/plot-dark.png +preview_html_light: null +preview_html_dark: null +quality_score: 85 review: strengths: - - Excellent implementation of rose chart using plotnine polygon geometry approach - - Clean visual design with subtle gridlines and well-positioned month labels - - Correct radius-proportional encoding as specified (not area) - - Proper clockwise arrangement starting from January at 12 o'clock - - Effective blue-to-yellow color gradient based on rainfall values - - Good use of gaps between wedges for visual separation - - Realistic monthly rainfall data with meaningful seasonal variation + - Viridis colormap double-encodes rainfall via both radius and color, making the + seasonal pattern immediately readable + - 'Full spec compliance: all required rose-chart features present (gridlines, spokes, + circular ordering, 12-o-clock start)' + - Theme-adaptive chrome works correctly in both renders with no dark-on-dark or + light-on-light failures + - 'Perfect data and code quality: realistic monthly rainfall scenario, deterministic, + clean linear structure' weaknesses: - - Value labels (20, 40, 60, 80, 100) are positioned only on one spoke, making them - less visible - - Figure size is 12x12 at 300 DPI (3600x3600) which is acceptable but the spec prefers - 16x9 for landscape or explicit 3600x3600 for square - - Could use plotnine built-in theme functions more extensively - image_description: The plot displays a rose chart (coxcomb diagram) showing monthly - rainfall data in millimeters. The chart features 12 wedge-shaped segments arranged - in a clockwise direction starting from January at the top (12 o'clock position). - Each wedge extends from the center with a radius proportional to the rainfall - value. The color scheme transitions from blue (lower values like March/April/May - around 48-55mm) through olive/green tones to yellow (higher values like November - at 95mm). Radial gridlines are visible as dashed circles at 20, 40, 60, 80, and - 100mm intervals. Spoke lines separate the months. Month labels (Jan, Feb, etc.) - are positioned outside the chart in bold black text. Value labels (20, 40, 60, - 80, 100) appear along the top spoke. The title "Monthly Rainfall (mm) · rose-basic - · plotnine · pyplots.ai" is centered at the top. The plot uses a square 1:1 aspect - ratio with a clean white background. + - 'Font sizes below recommended minimums: title 22pt (need >=24pt), month labels + 14pt (need >=16pt), value labels 10pt (quite small at 3600px)' + - matplotlib color utilities used to compute viridis hex values; native scale_fill_cmap(cmap_name=viridis) + with continuous fill aesthetic would be more idiomatic plotnine + image_description: |- + Light render (plot-light.png): + Background: Warm off-white #FAF8F1 -- correct + Chrome: Title "Monthly Rainfall (mm) * rose-basic * plotnine * anyplot.ai" in dark ink, readable; month labels in bold dark text clearly positioned outside perimeter; radial value labels (20-100) visible near top-right spoke; all readable + Data: 12 wedges colored via viridis gradient (deep purple for low values ~48mm, through teal/green, to bright yellow-green for high values ~95mm); white gaps between wedges; dashed radial grid circles at alpha=0.15; spoke lines at alpha=0.12; annotations "Peak 95 mm" on Nov and "Min 48 mm" on Apr visible + Legibility verdict: PASS + + Dark render (plot-dark.png): + Background: Near-black #1A1A17 -- correct + Chrome: Title and all text labels render in light colors (#F0EFE8 / #B8B7B0); no dark-on-dark failures observed; grid and spokes remain subtle + Data: Viridis colors identical to light render -- same purple-to-yellow-green gradient; both annotations visible with light text + Legibility verdict: PASS criteria_checklist: visual_quality: - score: 36 - max: 40 + score: 27 + max: 30 items: - id: VQ-01 name: Text Legibility - score: 9 - max: 10 + score: 5 + max: 8 passed: true - comment: Title and month labels are clearly readable, value labels slightly - small + comment: All sizes explicitly set; title 22pt below 24pt guideline, month + labels 14pt below 16pt guideline, value labels 10pt small - id: VQ-02 name: No Overlap - score: 8 - max: 8 + score: 6 + max: 6 passed: true - comment: No overlapping text elements + comment: Month labels evenly distributed around perimeter, no collisions - id: VQ-03 name: Element Visibility - score: 7 - max: 8 + score: 6 + max: 6 passed: true - comment: Wedges are well-sized and clearly visible with appropriate alpha + comment: All 12 wedges clearly visible with viridis gradient providing strong + differentiation - id: VQ-04 name: Color Accessibility - score: 4 - max: 5 + score: 2 + max: 2 passed: true - comment: Blue-to-yellow gradient is colorblind-friendly, though not using - a standard accessible palette + comment: Viridis is perceptually uniform and CVD-safe - id: VQ-05 - name: Layout Balance - score: 5 - max: 5 + name: Layout & Canvas + score: 4 + max: 4 passed: true - comment: Good use of square canvas, plot is well-centered with balanced margins + comment: Square 3600x3600 format; chart fills canvas well with balanced margins - id: VQ-06 - name: Axis Labels + name: Axis Labels & Title score: 2 max: 2 passed: true - comment: Title includes units "(mm)" - appropriate for rose chart without - traditional axes + comment: Title includes units (mm), descriptive and informative - id: VQ-07 - name: Grid & Legend - score: 1 + name: Palette Compliance + score: 2 max: 2 passed: true - comment: Grid is subtle and helpful, but legend is hidden (acceptable for - this single-series chart) + comment: 'Viridis correctly used for value-encoded colors; backgrounds #FAF8F1/#1A1A17 + correct; chrome theme-adaptive' + design_excellence: + score: 13 + max: 20 + items: + - id: DE-01 + name: Aesthetic Sophistication + score: 5 + max: 8 + passed: true + comment: 'Above default: viridis double-encodes rainfall, clean circular form, + white wedge gaps, no axes/spines' + - id: DE-02 + name: Visual Refinement + score: 4 + max: 6 + passed: true + comment: Dashed radial grid at alpha=0.15, spokes at alpha=0.12, legend suppressed, + axis elements blanked + - id: DE-03 + name: Data Storytelling + score: 4 + max: 6 + passed: true + comment: Color gradient creates visual hierarchy; rendered peak/min annotations + emphasize seasonal story spec_compliance: - score: 24 - max: 25 + score: 15 + max: 15 items: - id: SC-01 name: Plot Type - score: 8 - max: 8 - passed: true - comment: Correct rose/coxcomb chart implementation - - id: SC-02 - name: Data Mapping score: 5 max: 5 passed: true - comment: Radius correctly proportional to value (not area) - - id: SC-03 + comment: Correct rose/Nightingale chart; equal-angle wedges, radius proportional + to value + - id: SC-02 name: Required Features score: 4 - max: 5 + max: 4 passed: true - comment: Has radial gridlines, starts at 12 o'clock, clockwise arrangement; - small gaps between wedges add polish - - id: SC-04 - name: Data Range + comment: Radial gridlines, spoke lines, circular ordering, 12-o-clock start + all present + - id: SC-03 + name: Data Mapping score: 3 max: 3 passed: true - comment: All 12 months visible with appropriate scale - - id: SC-05 - name: Legend Accuracy - score: 2 - max: 2 - passed: true - comment: N/A (single series, legend appropriately hidden) - - id: SC-06 - name: Title Format - score: 2 - max: 2 + comment: Monthly rainfall correctly drives segment radius; all 12 categories + shown + - id: SC-04 + name: Title & Legend + score: 3 + max: 3 passed: true - comment: 'Correct format: "Monthly Rainfall (mm) · rose-basic · plotnine · - pyplots.ai"' + comment: Title contains all required components; months labeled directly, + no legend needed data_quality: - score: 19 - max: 20 + score: 15 + max: 15 items: - id: DQ-01 name: Feature Coverage - score: 7 - max: 8 + score: 6 + max: 6 passed: true - comment: Shows variation across months with clear seasonal patterns; could - benefit from more extreme contrasts + comment: Seasonal variation well represented; values span 48-95mm showing + clear pattern - id: DQ-02 name: Realistic Context - score: 7 - max: 7 + score: 5 + max: 5 passed: true - comment: Monthly rainfall for temperate climate is an excellent, real-world - scenario + comment: Monthly rainfall for temperate climate is realistic and neutral - id: DQ-03 name: Appropriate Scale - score: 5 - max: 5 + score: 4 + max: 4 passed: true - comment: Values (48-95mm) are realistic for monthly rainfall + comment: 48-95mm monthly totals plausible for temperate oceanic climate code_quality: - score: 9 + score: 10 max: 10 items: - id: CQ-01 @@ -173,42 +179,62 @@ review: score: 3 max: 3 passed: true - comment: 'Linear script: imports → data → plot construction → save' + comment: 'Linear: tokens -> data -> geometry DataFrames -> plot -> save; no + functions or classes' - id: CQ-02 name: Reproducibility - score: 3 - max: 3 + score: 2 + max: 2 passed: true - comment: Uses deterministic hardcoded data (no randomness) + comment: Deterministic hardcoded data; no random generation - id: CQ-03 name: Clean Imports score: 2 max: 2 passed: true - comment: All imports are used + comment: All imports used; sys.path fix commented; matplotlib color utilities + justified - id: CQ-04 - name: No Deprecated API - score: 1 - max: 1 + name: Code Elegance + score: 2 + max: 2 passed: true - comment: Uses current plotnine API + comment: Geometry loops necessary for this chart type in plotnine; appropriate + complexity - id: CQ-05 - name: Output Correct - score: 0 + name: Output & API + score: 1 max: 1 - passed: false - comment: Saves as 'plot.png' but figure_size is 12x12 (should be 16x9 or 12x12 - at dpi to achieve target resolution) - library_features: - score: 3 - max: 5 + passed: true + comment: Saves as plot-{THEME}.png; current plotnine 0.15.3 API + library_mastery: + score: 5 + max: 10 items: - - id: LF-01 - name: Uses distinctive library features + - id: LM-01 + name: Idiomatic Usage score: 3 max: 5 passed: true - comment: Uses plotnine's grammar of graphics with geom_polygon and layered - construction, but doesn't leverage coord_polar() since plotnine lacks it - - manual polygon construction is a workaround + comment: Correct ggplot grammar with layered geoms and theme; manual Cartesian + polygon approach bypasses polar support + - id: LM-02 + name: Distinctive Features + score: 2 + max: 5 + passed: false + comment: geom_polygon for wedges and scale_fill_manual with computed colors; + somewhat distinctive but not deeply plotnine-specific verdict: APPROVED +impl_tags: + dependencies: [] + techniques: + - layer-composition + - patches + patterns: + - data-generation + - iteration-over-groups + dataprep: [] + styling: + - alpha-blending + - minimal-chrome