diff --git a/plots/windrose-basic/implementations/python/seaborn.py b/plots/windrose-basic/implementations/python/seaborn.py index ea35fd4f07..9b5065996d 100644 --- a/plots/windrose-basic/implementations/python/seaborn.py +++ b/plots/windrose-basic/implementations/python/seaborn.py @@ -1,7 +1,7 @@ -""" anyplot.ai +"""anyplot.ai windrose-basic: Wind Rose Chart -Library: seaborn 0.13.2 | Python 3.13.13 -Quality: 88/100 | Updated: 2026-05-07 +Library: seaborn 0.13.2 | Python 3.13.14 +Quality: 80/100 | Updated: 2026-08-05 """ import os @@ -18,8 +18,9 @@ INK = "#1A1A17" if THEME == "light" else "#F0EFE8" INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0" -# Okabe-Ito palette for speed ranges (cool to warm progression) -IMPRINT = ["#009E73", "#C475FD", "#4467A3", "#BD8233", "#AE3030"] +# Imprint palette for speed ranges (cool to warm progression), built via +# seaborn's own palette API so downstream seaborn calls (kdeplot) share it. +IMPRINT = sns.color_palette(["#009E73", "#C475FD", "#4467A3", "#BD8233", "#AE3030"]) # Configure seaborn sns.set_theme( @@ -88,10 +89,14 @@ frequencies = frequencies / n_obs * 100 -# Plot -fig = plt.figure(figsize=(12, 12), facecolor=PAGE_BG) -ax = fig.add_subplot(111, projection="polar") - +# Plot: polar wind rose fills the square canvas; legend and a seaborn KDE +# inset sit in the corners that fall outside the circle, which the bars +# (clipped to the radial axis limit) never reach regardless of direction. +fig = plt.figure(figsize=(6, 6), dpi=400, facecolor=PAGE_BG) +# Axes deliberately smaller than the full canvas (vs. a near-full-bleed square) +# so the compass-label ring (just outside the circle) has real clearance from +# the figure's literal corners before the inset/legend boxes anchored there. +ax = fig.add_axes((0.17, 0.17, 0.66, 0.66), projection="polar") ax.set_facecolor(PAGE_BG) ax.set_theta_zero_location("N") ax.set_theta_direction(-1) @@ -103,51 +108,59 @@ dominant_threshold = np.percentile(total_freq, 75) is_dominant = total_freq > dominant_threshold -# Plot stacked bars with Okabe-Ito palette +# Plot stacked bars with the Imprint palette bottoms = np.zeros(n_dir_bins) for j, (label, color) in enumerate(zip(speed_labels, IMPRINT, strict=False)): - # Use full alpha for dominant sectors, reduced for weaker ones alpha_per_sector = np.where(is_dominant, 0.90, 0.65) - - # Plot all sectors in one call, then manually adjust alpha if possible + # Contiguous petals (full dir_width, no inter-sector gap): a gap here would + # leave a thin sliver of background between direction bins that, next to a + # tall dominant sector, reads as a stray line cutting across the chart. bars = ax.bar( theta, frequencies[:, j], - width=dir_width * 0.9, + width=dir_width, bottom=bottoms, color=color, edgecolor=PAGE_BG, - linewidth=0.5, + linewidth=0.7, label=label, - alpha=0.85, ) - - # Adjust individual bar alpha for dominant directions for bar, alpha_val in zip(bars, alpha_per_sector, strict=False): bar.set_alpha(alpha_val) - bottoms += frequencies[:, j] -# Style -ax.set_title("windrose-basic · seaborn · anyplot.ai", fontsize=24, pad=20, fontweight="medium", color=INK) +# Title +ax.set_title("windrose-basic · python · seaborn · anyplot.ai", fontsize=12, pad=16, fontweight="medium", color=INK) max_freq = np.ceil(bottoms.max() / 5) * 5 ax.set_ylim(0, max_freq) ax.set_yticks(np.arange(0, max_freq + 1, 5)) -ax.set_yticklabels([f"{int(y)}%" for y in np.arange(0, max_freq + 1, 5)], fontsize=14, color=INK_SOFT) +ax.set_yticklabels([f"{int(y)}%" for y in np.arange(0, max_freq + 1, 5)], fontsize=9, color=INK_SOFT) +# Move the radial-label spoke into the near-empty E/SE sector so it doesn't +# cut across the dominant, densely colored bars. +ax.set_rlabel_position(112.5) direction_labels = ["N", "NE", "E", "SE", "S", "SW", "W", "NW"] ax.set_xticks(np.deg2rad(np.arange(0, 360, 45))) -ax.set_xticklabels(direction_labels, fontsize=18, fontweight="medium", color=INK) +ax.set_xticklabels(direction_labels, fontsize=11, fontweight="medium", color=INK) -# Enhanced grid styling with subtle radial emphasis +# Grid styling ax.grid(True, alpha=0.12, linestyle="-", linewidth=0.8, color=INK_SOFT) for spine in ax.spines.values(): spine.set_color(INK_SOFT) spine.set_linewidth(1.1) +# Legend anchored to the figure's literal bottom-right corner (not the axes' +# own bounding box) so it sits well beyond the SE compass-label ring instead +# of overlapping it. legend = ax.legend( - title="Wind Speed", loc="lower right", bbox_to_anchor=(1.15, 0), fontsize=14, title_fontsize=16, framealpha=0.95 + title="Wind Speed", + loc="lower right", + bbox_to_anchor=(0.99, 0.01), + bbox_transform=fig.transFigure, + fontsize=8, + title_fontsize=9, + framealpha=0.95, ) legend.get_frame().set_facecolor(ELEVATED_BG) legend.get_frame().set_edgecolor(INK_SOFT) @@ -155,5 +168,19 @@ for text in legend.get_texts(): text.set_color(INK) -plt.tight_layout() -plt.savefig(f"plot-{THEME}.png", dpi=300, bbox_inches="tight", facecolor=PAGE_BG) +# Distinctive seaborn feature: a kernel-density estimate of the overall speed +# distribution, tucked fully into the figure's literal top-left corner (past +# the NW compass-label ring, not just past the data wedges) so it never +# occludes the label ring the polar axes draws around its full circle. +inset = fig.add_axes((0.015, 0.815, 0.155, 0.155)) +sns.kdeplot(x=speeds, fill=True, color=IMPRINT[0], alpha=0.55, linewidth=1.2, ax=inset) +inset.set_facecolor(PAGE_BG) +inset.set_title("Speed distribution", fontsize=8, color=INK, pad=3) +inset.set_xlabel("Wind speed (m/s)", fontsize=8, color=INK_SOFT) +inset.set_ylabel("") +inset.set_yticks([]) +inset.tick_params(axis="x", labelsize=8, colors=INK_SOFT) +sns.despine(ax=inset, left=True) +inset.spines["bottom"].set_color(INK_SOFT) + +fig.savefig(f"plot-{THEME}.png", dpi=400, facecolor=PAGE_BG) diff --git a/plots/windrose-basic/metadata/python/seaborn.yaml b/plots/windrose-basic/metadata/python/seaborn.yaml index f8332769e0..657e22f564 100644 --- a/plots/windrose-basic/metadata/python/seaborn.yaml +++ b/plots/windrose-basic/metadata/python/seaborn.yaml @@ -2,171 +2,190 @@ library: seaborn language: python specification_id: windrose-basic created: '2025-12-24T22:16:59Z' -updated: '2026-05-07T03:33:08Z' -generated_by: claude-haiku -workflow_run: 25474192715 +updated: '2026-08-05T15:41:27Z' +generated_by: claude-sonnet +workflow_run: 31018968906 issue: 1880 -python_version: 3.13.13 +language_version: 3.13.14 library_version: 0.13.2 preview_url_light: https://storage.googleapis.com/anyplot-images/plots/windrose-basic/python/seaborn/plot-light.png preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/windrose-basic/python/seaborn/plot-dark.png preview_html_light: null preview_html_dark: null -quality_score: 88 +quality_score: 80 review: strengths: - - 'Perfect visual quality: All 8 VQ criteria at maximum with explicit font sizing - and flawless theme adaptation' - - 'Impeccable readability in both themes: Light and dark renders both fully legible - with zero dark-on-dark or light-on-light issues' - - 'Complete spec compliance: All windrose features present and correct with proper - title and legend formatting' - - 'Excellent data quality: Realistic meteorological data with plausible Weibull-distributed - speeds and natural prevailing wind patterns' - - 'Thoughtful design choices: Alpha variation (0.90 dominant, 0.65 weaker) creates - visual hierarchy emphasizing prevailing SW winds' - - 'Clean, reproducible code: Deterministic data generation with explicit seed and - straightforward structure' - - 'Professional presentation: Proper typography hierarchy (24pt/18pt/14pt), balanced - layout, well-positioned legend on elevated background' + - 'Correct wind-rose chart type: 8-sector polar stacked histogram with a realistic + prevailing-SW wind pattern (secondary NE lobe) and Weibull-distributed speeds + per direction' + - Imprint categorical palette applied in exact canonical order across all 5 speed + bins, identical hues in light and dark renders, with fully theme-correct chrome + (background, ticks, legend, grid) in both themes + - 'Genuine data storytelling: alpha emphasis (0.90 vs 0.65) highlights the dominant + >75th-percentile sectors, and the seaborn kdeplot inset adds real distributional + context beyond the rose itself — an idiomatic, distinctive seaborn feature' + - 'Clean, reproducible, KISS-structured code: seed set, only used imports, correct + f''plot-{THEME}.png'' save API, no bbox_inches=''tight'', exact mandated title + string' + - Exact 2400x2400 canvas with no edge clipping on N/S/W/E labels or the outer circle weaknesses: - - 'Limited distinctive seaborn features: Wind rose requires custom matplotlib implementation; - seaborn''s role is primarily theming rather than leveraging distinctive plotting - capabilities (FacetGrid, hue patterns, etc.)' + - 'The NW compass label is completely hidden behind the opaque ''Speed distribution'' + KDE inset panel (fig.add_axes at (0.05, 0.72, 0.24, 0.20) with an opaque PAGE_BG + facecolor, added after the polar axes so it draws on top and occludes whatever + tick label sits underneath). The SE compass label is likewise crowded out / hidden + by the radial % tick labels (rlabel_position=112.5deg, which sits almost exactly + at the SE bearing) and the adjacent ''Wind Speed'' legend box occupying the same + lower-right corner. Confirmed by pixel-cropping both corners: neither ''NW'' nor + ''SE'' text is visible in either theme, even though N/NE/E/S/SW/W all render cleanly. + 2 of the 8 required compass directions are effectively erased — the code''s comments + assume these corners are ''empty'' but they still carry the fixed compass-label + ring around the full circle. Reposition the inset/legend/rlabel spoke (e.g. shrink + further into the true corner past the label radius, or explicitly check clearance + against the tick-label ring, not just the data wedges) so no compass label is + ever covered.' + - The KDE inset's own tick/label text (fontsize 6-7pt) is noticeably smaller than + the rest of the chart's typography and risks becoming illegible at ~400px mobile + width — bump to ~8-9pt or simplify the tick count. + - No explicit radial-axis title (e.g. 'Frequency (%)') — the '%' suffix on the tick + labels implies it, but an explicit label would remove any ambiguity about what + the radius encodes. image_description: |- Light render (plot-light.png): - Background: Warm off-white (#FAF8F1) - Chrome: Title in 24pt dark text, direction labels in 18pt dark, radial labels in 14pt soft gray — all clearly readable - Data: Green (#009E73, 0-3 m/s innermost), orange (#D55E00), blue (#0072B2), pink (#CC79A7), yellow (#E69F00, 15+ m/s outermost); SW sector dominates with full opacity (0.90), weaker sectors at reduced opacity (0.65); stacked composition clearly visible - Legibility verdict: PASS - All text crisp and fully readable; no overlap; contrast excellent + Background: Warm off-white (#FAF8F1), matches spec, not pure white. + Chrome: Title "windrose-basic · python · seaborn · anyplot.ai" in dark ink at top-left of the polar area, compact and fully readable. Compass labels N (top), NE (upper-right), E (right), S (bottom), SW (lower-left), W (left) all render clearly in dark ink against the cream background. Radial percentage ticks (5%-35%) legible in soft grey. Legend "Wind Speed" box (cream fill, dark border) bottom-right, all 5 entries readable. + Data: Stacked polar bars — brand green (0-3 m/s) innermost, then lavender (3-6), blue (6-10), ochre (10-15), matte red (15+ m/s, outermost) — matching Imprint canonical order with #009E73 first. Dominant SW-S sector reaches ~30-35%, secondary N-NE sector reaches ~12-15%, other sectors near-empty. KDE inset (top-left) shows a right-skewed green density curve for overall wind speed. + Legibility verdict: FAIL — pixel-cropping the top-left and lower-right corners confirms the "NW" compass label is completely hidden underneath the opaque KDE inset panel, and the "SE" compass label is hidden/crowded out by the radial tick-label cascade and the adjacent legend box. All other text is readable. Dark render (plot-dark.png): - Background: Warm near-black (#1A1A17) - Chrome: Title in light text (#F0EFE8), direction labels in light text, radial labels in soft light gray (#B8B7B0) — all clearly visible against dark background; critically, NO dark-on-dark text failures present - Data: Identical colors to light render (green, orange, blue, pink, yellow in same positions); opacity variation (0.90 dominant, 0.65 weaker) preserved - Legibility verdict: PASS - All elements visible; legend readable on elevated dark background (#242420); theme adaptation flawless; both renders equally readable + Background: Warm near-black (#1A1A17), matches spec, not pure black. + Chrome: Title and all visible compass labels (N, NE, E, S, SW, W) render in light ink (#F0EFE8), clearly legible against the dark background — no dark-on-dark failures observed. Radial ticks and legend box use theme-adaptive dark-elevated fill (#242420) with light text, fully readable. Grid lines subtle and visible. + Data: Identical hues to the light render — brand green, lavender, blue, ochre, matte red in the same canonical order and same stacking — confirms only chrome flipped between themes, not data colors. + Legibility verdict: FAIL — same NW/SE compass-label occlusion issue as the light render (KDE inset panel and legend/radial-label cluster cover the same angular positions in both themes, since geometry is theme-independent). All other chrome is theme-correct and legible. criteria_checklist: visual_quality: - score: 30 + score: 20 max: 30 items: - id: VQ-01 name: Text Legibility - score: 8 + score: 6 max: 8 passed: true - comment: All font sizes explicitly set (24pt, 18pt, 14pt); perfectly readable - in both themes + comment: All font sizes explicitly set and readable in both themes; tiny 6-7pt + inset tick labels are the only borderline-mobile-legibility concern - id: VQ-02 name: No Overlap - score: 6 + score: 2 max: 6 - passed: true - comment: No overlapping text; all labels fully separated + passed: false + comment: NW compass label fully hidden behind the opaque KDE inset panel; + SE compass label hidden/crowded by the radial tick labels and legend box + occupying the same corner - id: VQ-03 name: Element Visibility - score: 6 + score: 5 max: 6 passed: true - comment: Stacked bars clearly visible; colors distinct; opacity variation - optimal + comment: Stacked bars and KDE fill clearly visible and density-appropriate + (alpha emphasis on dominant sectors) - id: VQ-04 name: Color Accessibility score: 2 max: 2 passed: true - comment: Okabe-Ito palette CVD-safe; adequate contrast + comment: Imprint palette provides adequate CVD-safe contrast between adjacent + speed bins - id: VQ-05 name: Layout & Canvas - score: 4 + score: 1 max: 4 - passed: true - comment: Polar plot fills canvas beautifully; balanced margins + passed: false + comment: Good canvas fill overall, but the inset/legend/rlabel placement collides + with the compass tick-label ring, erasing 2 of 8 direction labels - id: VQ-06 name: Axis Labels & Title score: 2 max: 2 passed: true - comment: Title correct; labels descriptive with units + comment: Radial % ticks and inset 'Wind speed (m/s)' label are descriptive + with units - id: VQ-07 name: Palette Compliance score: 2 max: 2 passed: true - comment: 'First series #009E73; colors follow Okabe-Ito; backgrounds #FAF8F1 - and #1A1A17; theme-adaptive chrome in both' + comment: 'First series #009E73, canonical Imprint order for all 5 bins, theme-correct + chrome in both renders' design_excellence: score: 14 max: 20 items: - id: DE-01 name: Aesthetic Sophistication - score: 5 + score: 6 max: 8 - passed: true - comment: Thoughtful design with alpha variation; above defaults but not exceptional + comment: Deliberate composition (dominant-sector emphasis, corner-tucked KDE + panel, repositioned radial label spoke) goes beyond a configured default, + though the corner-collision bug undercuts the intentional-hierarchy claim - id: DE-02 name: Visual Refinement - score: 5 + score: 4 max: 6 - passed: true - comment: Custom grid styling; theme-aware spines; elegant legend frame + comment: Subtle grid, despined inset, generous margins; the label-occlusion + issue keeps this from 'every detail polished' - id: DE-03 name: Data Storytelling score: 4 max: 6 - passed: true - comment: Visual hierarchy through opacity guides reader to dominant SW winds + comment: Alpha emphasis on dominant sectors plus the KDE inset create real + visual hierarchy pointing at the prevailing-wind insight spec_compliance: - score: 15 + score: 14 max: 15 items: - id: SC-01 name: Plot Type score: 5 max: 5 - passed: true - comment: Correct wind rose with 8 directions and 5 speed bins + comment: Correct polar stacked-histogram wind rose - id: SC-02 name: Required Features - score: 4 + score: 3 max: 4 - passed: true - comment: 'All features present: sectors, bins, stacking, legend, percentages' + comment: 8 direction bins, 5 speed bins, N-at-top, legend, radial frequency + % all present, but 2 direction labels are functionally invisible - id: SC-03 name: Data Mapping score: 3 max: 3 - passed: true - comment: Polar angle = direction; radial = frequency; all data visible + comment: Direction=theta, frequency=radius, speed=stacked color, correctly + mapped - id: SC-04 name: Title & Legend score: 3 max: 3 - passed: true - comment: Title and legend correct format + comment: Exact mandated title string; legend labels match the speed bins exactly data_quality: - score: 15 + score: 14 max: 15 items: - id: DQ-01 name: Feature Coverage - score: 6 + score: 5 max: 6 - passed: true - comment: Shows all aspects of wind rose + comment: Dominant SW, secondary NE, minor W sectors with full speed-bin stacking, + plus KDE distribution shape - id: DQ-02 name: Realistic Context score: 5 max: 5 - passed: true - comment: Realistic weather data with plausible distributions + comment: Real, neutral meteorological wind-station scenario - id: DQ-03 name: Appropriate Scale score: 4 max: 4 - passed: true - comment: Wind speeds and proportions physically realistic + comment: Weibull-based speeds clipped to a realistic 0-25 m/s range, plausible + frequency percentages code_quality: score: 10 max: 10 @@ -175,58 +194,59 @@ review: name: KISS Structure score: 3 max: 3 - passed: true - comment: 'Linear: imports→data→plot→save' + comment: Flat script, no functions/classes - id: CQ-02 name: Reproducibility score: 2 max: 2 - passed: true - comment: Seed set (42); fully deterministic + comment: np.random.seed(42) - id: CQ-03 name: Clean Imports score: 2 max: 2 - passed: true - comment: All imports used + comment: Only os, matplotlib, numpy, seaborn, all used - id: CQ-04 name: Code Elegance score: 2 max: 2 - passed: true - comment: Clean, Pythonic, appropriate complexity + comment: Appropriately complex, no fake functionality - id: CQ-05 name: Output & API score: 1 max: 1 - passed: true - comment: Saves as plot-{THEME}.png + comment: Correct f'plot-{THEME}.png' save, no bbox_inches='tight' library_mastery: - score: 4 + score: 8 max: 10 items: - id: LM-01 name: Idiomatic Usage - score: 3 + score: 4 max: 5 - passed: true - comment: Proper seaborn theming, but wind rose requires matplotlib + comment: sns.set_theme/color_palette/kdeplot/despine used idiomatically; the + core rose itself is necessarily raw matplotlib since seaborn has no windrose + primitive - id: LM-02 name: Distinctive Features - score: 1 + score: 4 max: 5 - passed: false - comment: Limited distinctive seaborn usage; wind rose necessitates matplotlib - verdict: APPROVED + comment: The seaborn kdeplot inset is a genuine distinctive-library feature + tied into the wind rose, though somewhat tacked-on rather than deeply integrated + verdict: REJECTED impl_tags: dependencies: [] techniques: - polar-projection + - inset-axes - manual-ticks patterns: - data-generation - matrix-construction - dataprep: [] + - iteration-over-groups + dataprep: + - binning + - kde styling: - - grid-styling - alpha-blending + - edge-highlighting + - grid-styling