diff --git a/plots/cartogram-area-distortion/implementations/python/seaborn.py b/plots/cartogram-area-distortion/implementations/python/seaborn.py index 5393cd8c56..cd015f91de 100644 --- a/plots/cartogram-area-distortion/implementations/python/seaborn.py +++ b/plots/cartogram-area-distortion/implementations/python/seaborn.py @@ -1,20 +1,32 @@ -""" pyplots.ai +"""anyplot.ai cartogram-area-distortion: Cartogram with Area Distortion by Data Value -Library: seaborn 0.13.2 | Python 3.14.3 -Quality: 89/100 | Created: 2026-03-13 +Library: seaborn | Python 3.13 +Quality: pending | Created: 2026-06-08 """ +import os + import matplotlib.pyplot as plt import numpy as np import pandas as pd import seaborn as sns -# Data: US states with population (millions) and grid positions +# Theme tokens +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" + +# Imprint palette — first series always #009E73 +IMPRINT_PALETTE = ["#009E73", "#C475FD", "#4467A3", "#BD8233", "#AE3030", "#2ABCCD", "#954477", "#99B314"] + +# Data: US states with population (millions) and approximate grid positions np.random.seed(42) states_data = { - # (row, col, population_millions, region) "WA": (0, 1, 7.7, "West"), "MT": (0, 3, 1.1, "West"), "ND": (0, 5, 0.8, "Midwest"), @@ -67,35 +79,56 @@ "HI": (5, 2, 1.4, "West"), } -# Build dataframe -rows = [] +rows_data = [] for state, (r, c, pop, region) in states_data.items(): - rows.append({"state": state, "row": r, "col": c, "population": pop, "region": region}) -df = pd.DataFrame(rows) - -# Set minimum marker size so smallest states remain readable -size_min = 150 -size_max = 4200 + rows_data.append({"state": state, "row": r, "col": c, "population": pop, "region": region}) +df = pd.DataFrame(rows_data) -# Region ordering and seaborn colorblind palette +# Region colors using Imprint palette (West→green, Midwest→lavender, South→blue, Northeast→ochre) region_order = ["West", "Midwest", "South", "Northeast"] -region_palette = dict(zip(region_order, sns.color_palette("colorblind", n_colors=4), strict=False)) +region_palette = dict(zip(region_order, IMPRINT_PALETTE[:4], strict=False)) -# Setup theme using seaborn's set_theme with custom rc params +# Apply seaborn theme with theme-adaptive tokens sns.set_theme( style="white", - context="talk", - font_scale=1.15, - rc={"figure.facecolor": "#f5f5f5", "axes.facecolor": "#f5f5f5", "font.family": "sans-serif"}, + rc={ + "figure.facecolor": PAGE_BG, + "axes.facecolor": PAGE_BG, + "axes.edgecolor": INK_SOFT, + "axes.labelcolor": INK, + "text.color": INK, + "xtick.color": INK_SOFT, + "ytick.color": INK_SOFT, + "grid.color": INK, + "grid.alpha": 0.15, + "legend.facecolor": ELEVATED_BG, + "legend.edgecolor": INK_SOFT, + }, ) -fig = plt.figure(figsize=(16, 9)) -gs = fig.add_gridspec(2, 2, width_ratios=[3.2, 1], height_ratios=[1, 1], wspace=0.08, hspace=0.25) +# Canvas — exactly 3200×1800 px (landscape 16:9) +fig = plt.figure(figsize=(8, 4.5), dpi=400, facecolor=PAGE_BG) +gs = fig.add_gridspec( + 2, + 2, + width_ratios=[3.0, 1.2], + height_ratios=[1, 1], + wspace=0.12, + hspace=0.32, + left=0.01, + right=0.99, + top=0.89, + bottom=0.04, +) ax_main = fig.add_subplot(gs[:, 0]) ax_ref = fig.add_subplot(gs[0, 1]) ax_bar = fig.add_subplot(gs[1, 1]) -# Main cartogram using sns.scatterplot with size and hue encoding +# Marker sizes calibrated for 3200×1800 canvas +size_min = 20 +size_max = 620 + +# Main cartogram: bubble area ∝ population, color = geographic region sns.scatterplot( data=df, x="col", @@ -108,46 +141,51 @@ style="region", style_order=region_order, markers=dict.fromkeys(region_order, "s"), - alpha=0.85, - edgecolor="white", - linewidth=1.5, + alpha=0.88, + edgecolor=PAGE_BG, + linewidth=0.5, ax=ax_main, ) -# Extract region-only handles from auto-generated legend, then reposition as compact horizontal +# Compact region legend — keep only hue (region) handles, remove size handles handles, labels = ax_main.get_legend_handles_labels() -region_handles = [] -region_labels = [] +region_handles, region_labels = [], [] +seen = set() for handle, lbl in zip(handles, labels, strict=False): - if lbl in region_order: - handle.set_markersize(12) - handle.set_markeredgecolor("white") - handle.set_markeredgewidth(1) + if lbl in region_order and lbl not in seen: + try: + handle.set_markersize(7) + handle.set_markeredgecolor(PAGE_BG) + handle.set_markeredgewidth(0.4) + except AttributeError: + pass region_handles.append(handle) region_labels.append(lbl) + seen.add(lbl) ax_main.get_legend().remove() ax_main.legend( handles=region_handles, labels=region_labels, loc="upper center", - fontsize=12, + fontsize=5.5, title="Region", - title_fontsize=13, + title_fontsize=6, framealpha=0.9, - edgecolor="#cccccc", + facecolor=ELEVATED_BG, + edgecolor=INK_SOFT, ncol=4, - bbox_to_anchor=(0.45, 1.02), - borderpad=0.6, - columnspacing=1.0, - handletextpad=0.4, + bbox_to_anchor=(0.42, 1.0), + borderpad=0.4, + columnspacing=0.7, + handletextpad=0.3, ) -# State abbreviation labels on main cartogram +# State abbreviation labels — size scales with population pop_max = df["population"].max() for _, row in df.iterrows(): pop_frac = row["population"] / pop_max - fontsize = max(10, min(18, int(11 + pop_frac * 8))) + fontsize = max(4, min(7, int(4 + pop_frac * 3.5))) ax_main.text( row["col"], row["row"] - 0.03, @@ -159,53 +197,49 @@ color="white", zorder=5, ) - # Population label for larger states - if row["population"] >= 5.0: + if row["population"] >= 8.0: ax_main.text( row["col"], row["row"] + 0.22, f"{row['population']:.0f}M", ha="center", va="center", - fontsize=max(8, int(fontsize * 0.6)), + fontsize=max(3.5, int(fontsize * 0.65)), color="white", alpha=0.9, zorder=5, ) -# Style main axes ax_main.invert_yaxis() -ax_main.set_aspect("equal") ax_main.set_xlim(-0.8, 13.5) ax_main.set_ylim(5.8, -0.7) ax_main.set_xlabel("") ax_main.set_ylabel("") ax_main.set_xticks([]) ax_main.set_yticks([]) +ax_main.set_facecolor(PAGE_BG) sns.despine(ax=ax_main, left=True, bottom=True) -# Title - at least 24pt per quality criteria VQ-01 -ax_main.set_title( - "US States by Population\ncartogram-area-distortion \u00b7 seaborn \u00b7 pyplots.ai", - fontsize=24, - fontweight="bold", - pad=18, -) +# Title — scale fontsize for long title string +title = "US States by Population · cartogram-area-distortion · python · seaborn · anyplot.ai" +n = len(title) +ratio = 67 / n if n > 67 else 1.0 +title_fontsize = max(8, round(12 * ratio)) +ax_main.set_title(title, fontsize=title_fontsize, fontweight="medium", color=INK, pad=8) -# Size annotation ax_main.text( 0.98, 0.02, - "Tile area \u221d state population", + "Tile area ∝ state population", ha="right", va="bottom", - fontsize=13, - color="#666666", + fontsize=5.5, + color=INK_MUTED, fontstyle="italic", transform=ax_main.transAxes, ) -# --- Reference inset: equal-size tile map using seaborn's scatterplot with hue --- +# Reference inset — equal-area tile map for comparison sns.scatterplot( data=df, x="col", @@ -216,15 +250,14 @@ style="region", style_order=region_order, markers=dict.fromkeys(region_order, "s"), - s=120, - alpha=0.7, - edgecolor="white", - linewidth=0.8, + s=22, + alpha=0.72, + edgecolor=PAGE_BG, + linewidth=0.3, legend=False, ax=ax_ref, ) -# Labels on reference map - increased to 8pt for readability for _, row in df.iterrows(): ax_ref.text( row["col"], @@ -232,42 +265,26 @@ row["state"], ha="center", va="center", - fontsize=8, + fontsize=3.5, fontweight="bold", color="white", zorder=5, ) ax_ref.invert_yaxis() -ax_ref.set_aspect("equal") ax_ref.set_xlim(-0.5, 13.0) ax_ref.set_ylim(5.8, -0.5) ax_ref.set_xlabel("") ax_ref.set_ylabel("") ax_ref.set_xticks([]) ax_ref.set_yticks([]) +ax_ref.set_facecolor(PAGE_BG) sns.despine(ax=ax_ref, left=True, bottom=True) -ax_ref.set_title("Equal-Area\nReference", fontsize=13, fontweight="bold", pad=8) +ax_ref.set_title("Equal-Area Reference", fontsize=7.5, fontweight="bold", color=INK, pad=4) -# Subtle divider line between main and reference/bar panels -divider_x = 0.74 -fig.add_artist( - plt.Line2D( - [divider_x, divider_x], - [0.05, 0.92], - transform=fig.transFigure, - color="#cccccc", - linewidth=1, - linestyle="--", - alpha=0.6, - ) -) - -# --- Population by region: seaborn barplot with statistical aggregation --- -# Aggregate total population per region - leverages seaborn's categorical plotting +# Regional population totals: horizontal bar chart region_totals = df.groupby("region", observed=True)["population"].sum().reset_index() region_totals.columns = ["region", "total_pop"] -region_totals["total_pop"] = region_totals["total_pop"].round(1) sns.barplot( data=region_totals, @@ -277,34 +294,25 @@ hue_order=region_order, order=region_order, palette=region_palette, - edgecolor="white", - linewidth=1.2, + edgecolor=PAGE_BG, + linewidth=0.8, legend=False, ax=ax_bar, - saturation=0.85, ) -# Add value labels on bars -for i, (_, rrow) in enumerate(region_totals.set_index("region").reindex(region_order).iterrows()): - ax_bar.text( - rrow["total_pop"] + 0.8, - i, - f"{rrow['total_pop']:.0f}M", - ha="left", - va="center", - fontsize=11, - fontweight="bold", - color="#444444", - ) +for i, region_name in enumerate(region_order): + val = region_totals.loc[region_totals["region"] == region_name, "total_pop"].values[0] + ax_bar.text(val + 1.0, i, f"{val:.0f}M", ha="left", va="center", fontsize=6.5, fontweight="bold", color=INK_SOFT) -ax_bar.set_xlabel("Total Population (M)", fontsize=12) -ax_bar.set_ylabel("") -ax_bar.set_title("Regional Totals", fontsize=13, fontweight="bold", pad=8) -ax_bar.tick_params(axis="y", labelsize=11) -ax_bar.tick_params(axis="x", labelsize=10) +ax_bar.set_xlabel("Total Population (M)", fontsize=7.5, color=INK) +ax_bar.set_ylabel("", color=INK) +ax_bar.set_title("Regional Totals", fontsize=8, fontweight="bold", color=INK, pad=4) +ax_bar.tick_params(axis="y", labelsize=7, colors=INK_SOFT) +ax_bar.tick_params(axis="x", labelsize=6.5, colors=INK_SOFT) ax_bar.set_xlim(0, region_totals["total_pop"].max() * 1.25) +ax_bar.set_facecolor(PAGE_BG) sns.despine(ax=ax_bar, left=True) -ax_bar.yaxis.grid(False) -ax_bar.xaxis.grid(True, alpha=0.15, linewidth=0.8) +ax_bar.xaxis.grid(True, alpha=0.15, linewidth=0.6, color=INK) -plt.savefig("plot.png", dpi=300, bbox_inches="tight") +# Save — no bbox_inches='tight' per seaborn canvas rules +plt.savefig(f"plot-{THEME}.png", dpi=400, facecolor=PAGE_BG) diff --git a/plots/cartogram-area-distortion/metadata/python/seaborn.yaml b/plots/cartogram-area-distortion/metadata/python/seaborn.yaml index 8c58e85ecf..654d1eb1ad 100644 --- a/plots/cartogram-area-distortion/metadata/python/seaborn.yaml +++ b/plots/cartogram-area-distortion/metadata/python/seaborn.yaml @@ -1,242 +1,21 @@ +# Per-library metadata for seaborn implementation of cartogram-area-distortion +# Auto-generated by impl-generate.yml + library: seaborn +language: python specification_id: cartogram-area-distortion created: '2026-03-13T21:20:20Z' -updated: '2026-03-13T21:53:27Z' -generated_by: claude-opus-4-5-20251101 -workflow_run: 23070855374 +updated: '2026-06-08T03:40:04Z' +generated_by: claude-sonnet +workflow_run: 27114302363 issue: 4671 -python_version: 3.14.3 +language_version: 3.13.13 library_version: 0.13.2 -preview_url: https://storage.googleapis.com/anyplot-images/plots/cartogram-area-distortion/seaborn/plot.png -preview_html: null -quality_score: 89 +preview_url_light: https://storage.googleapis.com/anyplot-images/plots/cartogram-area-distortion/python/seaborn/plot-light.png +preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/cartogram-area-distortion/python/seaborn/plot-dark.png +preview_html_light: null +preview_html_dark: null +quality_score: null review: - strengths: - - 'Excellent three-panel layout: main cartogram + reference map + regional totals - bar chart creates a comprehensive visualization dashboard' - - Strong data storytelling through size variation, reference comparison, and aggregate - bar chart - - Clean colorblind-safe palette with professional polish (warm background, white - edges, subtle divider) - - Well-chosen data with realistic US state populations providing clear visual contrast - - Dynamic font sizing based on population fraction is an elegant touch - weaknesses: - - Bar chart and reference map text sizes are below recommended thresholds (12pt/10pt - vs 20pt/16pt guidelines) - - Some smaller states (VT, WY, SD) have tight marker spacing in the main cartogram - - Reference map labels at 8pt are very small, reducing its utility as a comparison - reference - image_description: 'The plot displays a tile grid cartogram of US states where each - state is represented by a colored square marker whose area is proportional to - its population. The main panel (left ~70% of canvas) shows states arranged in - an approximate geographic layout with a warm gray (#f5f5f5) background. States - are colored by region: blue (West), golden/yellow (Midwest), teal/green (South), - and orange/brown (Northeast) using seaborn''s colorblind palette. White bold abbreviations - label each state, and states with population >=5M also show population figures - (e.g., "39M" for CA, "30M" for TX). California and Texas are the largest squares, - visually dominating. A horizontal legend at the top shows all four regions. The - subtitle reads "cartogram-area-distortion · seaborn · pyplots.ai". The top-right - panel shows an equal-area reference tile map (all states same size) for comparison. - The bottom-right panel shows a horizontal bar chart of regional population totals - with value labels (South 126M, West 78M, Midwest 69M, Northeast 58M). A subtle - dashed divider separates the main and side panels. An italic annotation "Tile - area ∝ state population" appears at the bottom of the main cartogram.' - criteria_checklist: - visual_quality: - score: 27 - max: 30 - items: - - id: VQ-01 - name: Text Legibility - score: 7 - max: 8 - passed: true - comment: Title 24pt bold, state labels 10-18pt dynamic, but bar chart xlabel - 12pt and ticks 10-11pt are below 20pt/16pt thresholds - - id: VQ-02 - name: No Overlap - score: 5 - max: 6 - passed: true - comment: Most elements well-spaced; some tight spacing around smaller states - (WY, VT, SD) and dense reference map - - id: VQ-03 - name: Element Visibility - score: 5 - max: 6 - passed: true - comment: Square markers well-adapted with 150-4200 size range; smallest states - visible but small - - id: VQ-04 - name: Color Accessibility - score: 4 - max: 4 - passed: true - comment: Seaborn colorblind palette provides excellent distinguishability - across all four regions - - id: VQ-05 - name: Layout & Canvas - score: 4 - max: 4 - passed: true - comment: Excellent three-panel layout with main cartogram taking ~70% of canvas, - balanced margins - - id: VQ-06 - name: Axis Labels & Title - score: 2 - max: 2 - passed: true - comment: Descriptive title, bar chart has units (Total Population M), cartogram - appropriately omits axes - design_excellence: - score: 16 - max: 20 - items: - - id: DE-01 - name: Aesthetic Sophistication - score: 6 - max: 8 - passed: true - comment: Strong design with colorblind palette, warm background, white edge - highlighting, professional multi-panel layout - - id: DE-02 - name: Visual Refinement - score: 5 - max: 6 - passed: true - comment: All spines removed, subtle x-grid on bar chart only, warm background, - white marker edges, clean panels - - id: DE-03 - name: Data Storytelling - score: 5 - max: 6 - passed: true - comment: Size variation creates clear hierarchy, reference map comparison - reinforces concept, regional bar chart adds context - spec_compliance: - score: 14 - max: 15 - items: - - id: SC-01 - name: Plot Type - score: 4 - max: 5 - passed: true - comment: Tile grid cartogram with area proportional to population; valid variant - but not contiguous polygon distortion - - id: SC-02 - name: Required Features - score: 4 - max: 4 - passed: true - comment: 'All spec features present: area encoding, reference map, color by - region, labels, legend' - - id: SC-03 - name: Data Mapping - score: 3 - max: 3 - passed: true - comment: Population drives size, region drives color, grid positions approximate - geography - - id: SC-04 - name: Title & Legend - score: 3 - max: 3 - passed: true - comment: Correct title format with spec-id · library · pyplots.ai, region - legend matches data - data_quality: - score: 15 - max: 15 - items: - - id: DQ-01 - name: Feature Coverage - score: 6 - max: 6 - passed: true - comment: Wide population range 0.6M-39M spanning 65x variation across four - regions - - id: DQ-02 - name: Realistic Context - score: 5 - max: 5 - passed: true - comment: US state population is real-world, neutral, and comprehensible with - realistic values - - id: DQ-03 - name: Appropriate Scale - score: 4 - max: 4 - passed: true - comment: All population values realistic for current US states - code_quality: - score: 10 - max: 10 - items: - - id: CQ-01 - name: KISS Structure - score: 3 - max: 3 - passed: true - comment: 'Linear flow: imports, data, theme, figure, plots, save. No functions - or classes' - - id: CQ-02 - name: Reproducibility - score: 2 - max: 2 - passed: true - comment: np.random.seed(42) set, data is hardcoded and deterministic - - id: CQ-03 - name: Clean Imports - score: 2 - max: 2 - passed: true - comment: All four imports (matplotlib, numpy, pandas, seaborn) are used - - id: CQ-04 - name: Code Elegance - score: 2 - max: 2 - passed: true - comment: Clean code with elegant data dictionary approach and dynamic font - sizing - - id: CQ-05 - name: Output & API - score: 1 - max: 1 - passed: true - comment: Saves as plot.png with dpi=300, uses current seaborn API - library_mastery: - score: 7 - max: 10 - items: - - id: LM-01 - name: Idiomatic Usage - score: 4 - max: 5 - passed: true - comment: 'Good use of seaborn axes-level API: scatterplot with hue/size/style, - barplot, set_theme, despine, color_palette' - - id: LM-02 - name: Distinctive Features - score: 3 - max: 5 - passed: true - comment: Uses seaborn-specific features (set_theme, colorblind palette, despine, - multi-encoding scatterplot) but significant matplotlib for layout - verdict: REJECTED -impl_tags: - dependencies: [] - techniques: - - annotations - - subplots - - custom-legend - patterns: - - data-generation - - groupby-aggregation - - iteration-over-groups - - explicit-figure - dataprep: [] - styling: - - alpha-blending - - edge-highlighting - - grid-styling + strengths: [] + weaknesses: []