diff --git a/plots/heatmap-basic/implementations/plotnine.py b/plots/heatmap-basic/implementations/plotnine.py index f28feba654..d3cc860b10 100644 --- a/plots/heatmap-basic/implementations/plotnine.py +++ b/plots/heatmap-basic/implementations/plotnine.py @@ -1,7 +1,7 @@ """ pyplots.ai heatmap-basic: Basic Heatmap -Library: plotnine 0.15.2 | Python 3.13.11 -Quality: 92/100 | Created: 2025-12-23 +Library: plotnine 0.15.3 | Python 3.14.3 +Quality: 92/100 | Updated: 2026-02-16 """ import numpy as np @@ -9,67 +9,90 @@ from plotnine import ( aes, element_blank, + element_rect, element_text, geom_text, geom_tile, ggplot, labs, + scale_color_identity, scale_fill_gradient2, + scale_x_discrete, + scale_y_discrete, theme, theme_minimal, ) -# Data - 8x8 matrix with meaningful patterns (performance metrics by region and quarter) +# Data - 8x8 matrix: quarterly growth rates (%) by department np.random.seed(42) -rows = ["Region A", "Region B", "Region C", "Region D", "Region E", "Region F", "Region G", "Region H"] -cols = ["Q1 2023", "Q2 2023", "Q3 2023", "Q4 2023", "Q1 2024", "Q2 2024", "Q3 2024", "Q4 2024"] +departments = ["Engineering", "Marketing", "Sales", "Finance", "Operations", "HR", "Research", "Support"] +quarters = ["Q1 '23", "Q2 '23", "Q3 '23", "Q4 '23", "Q1 '24", "Q2 '24", "Q3 '24", "Q4 '24"] -# Create data with a trend and variation -base_values = np.linspace(-30, 40, 8) # Trend across columns -row_effects = np.random.uniform(-10, 10, 8) # Row-specific offsets -values = np.zeros((8, 8)) -for i in range(8): - for j in range(8): - values[i, j] = base_values[j] + row_effects[i] + np.random.uniform(-8, 8) +# Vectorized growth rates with recovery trend and departmental variation +base_trend = np.linspace(-18, 22, 8) +dept_offsets = np.array([-6, 10, 14, -3, 4, -10, 8, -5]) +values = np.round(base_trend[np.newaxis, :] + dept_offsets[:, np.newaxis] + np.random.normal(0, 4, (8, 8)), 1) -# Create long-form DataFrame for plotnine -data = [] -for i, row in enumerate(rows): - for j, col in enumerate(cols): - data.append({"x": col, "y": row, "value": round(values[i, j], 1)}) +# Inject distinctive extreme values for storytelling focal points +values[5, 0] = -32.5 # HR deep crisis in Q1 '23 +values[2, 7] = 38.2 # Sales strong recovery in Q4 '24 +values[6, 6] = 33.7 # Research surge in Q3 '24 -df = pd.DataFrame(data) +# Build long-form DataFrame via meshgrid indexing +dept_idx, qtr_idx = np.meshgrid(np.arange(8), np.arange(8), indexing="ij") +df = pd.DataFrame( + { + "Department": pd.Categorical( + [departments[i] for i in dept_idx.ravel()], categories=departments[::-1], ordered=True + ), + "Quarter": pd.Categorical([quarters[j] for j in qtr_idx.ravel()], categories=quarters, ordered=True), + "Growth (%)": values.ravel(), + } +) + +# Conditional text color: white on dark blue, dark gray on mid, dark brown on gold +df["text_color"] = np.where(df["Growth (%)"] < -12, "white", np.where(df["Growth (%)"] < 18, "#3a3a3a", "#4a2e00")) -# Preserve ordering -df["x"] = pd.Categorical(df["x"], categories=cols, ordered=True) -df["y"] = pd.Categorical(df["y"], categories=rows[::-1], ordered=True) # Reverse for top-to-bottom +# Signed annotation labels +df["label"] = [f"{v:+.1f}" for v in df["Growth (%)"]] # Plot plot = ( - ggplot(df, aes(x="x", y="y", fill="value")) - + geom_tile(color="white", size=0.5) - + geom_text(aes(label="value"), size=12, color="black") + ggplot(df, aes(x="Quarter", y="Department")) + + geom_tile(aes(fill="Growth (%)"), color="white", size=1.2) + + geom_text(aes(label="label", color="text_color"), size=10, fontweight="bold", show_legend=False) + scale_fill_gradient2( - low="#306998", # Python Blue for negative - mid="white", - high="#FFD43B", # Python Yellow for positive - midpoint=0, - name="Value", + low="#14405e", mid="#ede8e3", high="#c47d00", midpoint=0, name="Growth (%)", limits=(-35, 40) + ) + + scale_color_identity() + + scale_x_discrete(expand=(0, 0.5)) + + scale_y_discrete(expand=(0, 0.5)) + + labs( + x="Quarter", + y="Department", + title="Quarterly Growth by Department · heatmap-basic · plotnine · pyplots.ai", + subtitle="Year-over-year growth rate (%) across departments, Q1 2023 – Q4 2024", ) - + labs(x="Time Period", y="Region", title="heatmap-basic · plotnine · pyplots.ai") + theme_minimal() + theme( figure_size=(16, 9), - text=element_text(size=14), - axis_title=element_text(size=20), - axis_text_x=element_text(size=14, rotation=45, ha="right"), - axis_text_y=element_text(size=16), - plot_title=element_text(size=24), - legend_title=element_text(size=16), + text=element_text(family="sans-serif"), + plot_title=element_text(size=24, ha="center", weight="bold", margin={"b": 2}), + plot_subtitle=element_text(size=16, ha="center", color="#555555", margin={"b": 8}), + axis_title_x=element_text(size=20, margin={"t": 10}), + axis_title_y=element_text(size=20, margin={"r": 8}), + axis_text_x=element_text(size=16, rotation=45, ha="right", margin={"t": 4}), + axis_text_y=element_text(size=16, ha="right", margin={"r": 4}), + legend_title=element_text(size=16, weight="bold"), legend_text=element_text(size=14), + legend_position="right", + legend_key_height=40, panel_grid_major=element_blank(), panel_grid_minor=element_blank(), + panel_background=element_rect(fill="white", color="none"), + plot_background=element_rect(fill="#f7f7f7", color="none"), + plot_margin=0.02, ) ) diff --git a/plots/heatmap-basic/metadata/plotnine.yaml b/plots/heatmap-basic/metadata/plotnine.yaml index 9263e7671c..d1098bff26 100644 --- a/plots/heatmap-basic/metadata/plotnine.yaml +++ b/plots/heatmap-basic/metadata/plotnine.yaml @@ -1,12 +1,12 @@ library: plotnine specification_id: heatmap-basic created: '2025-12-23T00:47:36Z' -updated: '2025-12-23T00:51:16Z' -generated_by: claude-opus-4-5-20251101 +updated: '2026-02-16T20:34:16Z' +generated_by: claude-opus-4-6 workflow_run: 20447967616 issue: 0 -python_version: 3.13.11 -library_version: 0.15.2 +python_version: 3.14.3 +library_version: 0.15.3 preview_url: https://storage.googleapis.com/pyplots-images/plots/heatmap-basic/plotnine/plot.png preview_thumb: https://storage.googleapis.com/pyplots-images/plots/heatmap-basic/plotnine/plot_thumb.png preview_html: null @@ -14,159 +14,168 @@ quality_score: 92 impl_tags: dependencies: [] techniques: - - colorbar - annotations - layer-composition patterns: - data-generation - - wide-to-long + - matrix-construction dataprep: [] styling: + - custom-colormap - edge-highlighting review: strengths: - - Excellent diverging color scheme using Python brand colors (blue/yellow) that - is colorblind-safe - - Clear value annotations in each cell make exact values easy to read - - Well-structured data with meaningful trend pattern showing performance improvement - over time - - Clean use of plotnine grammar of graphics with proper layering (geom_tile + geom_text) - - Correct categorical ordering preserves temporal sequence on x-axis and intuitive - top-to-bottom region ordering - - Title follows exact specification format + - Excellent diverging color palette (steel blue to cream to gold) with well-chosen + midpoint at 0, creating an intuitive visual encoding of positive/negative growth + - Conditional text color logic (white on dark, dark gray on mid, dark brown on gold) + ensures all annotations are readable against their cell background + - Data tells a compelling recovery story with intentional extreme values (HR -32.5 + crisis, Sales +38.2 surge) serving as visual focal points + - Clean, idiomatic plotnine code with vectorized data generation and meshgrid-based + long-form DataFrame construction + - 'Fully polished theme: removed grid lines, custom background, generous margins, + rotated tick labels' weaknesses: - - Axis labels lack units or additional context (e.g., Value could specify Performance - Index or similar) - - Cell annotation text color is always black, which reduces contrast on darker blue - cells (e.g., Region G, Q1 2023 at -45.4) - image_description: The heatmap displays an 8x8 matrix showing performance metrics - across 8 regions (A-H on the y-axis) and 8 time periods (Q1 2023 through Q4 2024 - on the x-axis). The color scheme uses a diverging palette with Python Blue (#306998) - for negative values and Python Yellow (#FFD43B) for positive values, with white - at the midpoint (0). Each cell contains its numerical value as a text annotation. - The colorbar legend on the right shows the "Value" scale ranging from approximately - -25 to +25. The title "heatmap-basic · plotnine · pyplots.ai" appears at the top. - The plot clearly shows a trend pattern where earlier quarters have predominantly - negative values (blue) and later quarters show positive values (yellow), demonstrating - regional performance improvement over time. + - The colorbar legend could benefit from more tick marks to better communicate the + full range of the scale + - Minor wasted horizontal space on the right side between the plot and the legend + area + image_description: 'The heatmap displays an 8x8 grid of quarterly growth rates (%) + across 8 departments (Engineering, Marketing, Sales, Finance, Operations, HR, + Research, Support) over 8 quarters (Q1 ''23 through Q4 ''24). A diverging color + scheme is used: dark steel blue for strongly negative values, cream/beige for + near-zero values, and gold/amber for strongly positive values. Each cell contains + a bold, signed numeric annotation (e.g., "+38.2", "-32.5"). Text color adapts + to background: white on dark blue cells, dark gray on mid-range cells, and dark + brown on gold cells. The title "Quarterly Growth by Department · heatmap-basic + · plotnine · pyplots.ai" is centered and bold at the top, with a subtitle describing + the data. X-axis labels are rotated 45 degrees showing quarter names, Y-axis shows + department names. A vertical colorbar legend labeled "Growth (%)" appears on the + right with a range from approximately -20 to 40. White borders separate cells + cleanly. The background is light gray (#f7f7f7). The plot clearly shows a recovery + narrative from left (negative) to right (positive).' criteria_checklist: visual_quality: - score: 37 - max: 40 + score: 28 + max: 30 items: - id: VQ-01 name: Text Legibility - score: 10 - max: 10 + score: 8 + max: 8 passed: true - comment: Title at 24pt, axis labels at 20pt, tick labels at 14-16pt, cell - annotations at size 12 - all clearly readable + comment: 'All font sizes explicitly set: title 24pt, axis labels 20pt, ticks + 16pt, annotations bold size=10' - id: VQ-02 name: No Overlap - score: 8 - max: 8 + score: 6 + max: 6 passed: true - comment: No overlapping text elements, x-axis labels rotated 45° to avoid - collision + comment: No overlapping text anywhere, rotated x-axis labels, well-spaced + annotations - id: VQ-03 name: Element Visibility - score: 8 - max: 8 + score: 6 + max: 6 passed: true - comment: Tiles are well-sized with clear white borders, annotations visible - on all backgrounds + comment: Tiles fill grid perfectly, annotations clearly visible in every cell - id: VQ-04 name: Color Accessibility - score: 5 - max: 5 + score: 3 + max: 4 passed: true - comment: Blue-white-yellow diverging palette is colorblind-safe (avoids red-green) + comment: Diverging blue-cream-gold avoids red-green issues; not a standard + perceptually-uniform palette - id: VQ-05 name: Layout Balance - score: 4 - max: 5 + score: 3 + max: 4 passed: true - comment: Good proportions, slight white space on right side near colorbar - but acceptable + comment: Good canvas utilization, minor wasted space near legend on right - id: VQ-06 - name: Axis Labels - score: 1 + name: Axis Labels & Title + score: 2 max: 2 passed: true - comment: '"Time Period" and "Region" are descriptive but lack units' - - id: VQ-07 - name: Grid & Legend - score: 1 - max: 2 + comment: Descriptive labels with units in annotations and legend + design_excellence: + score: 16 + max: 20 + items: + - id: DE-01 + name: Aesthetic Sophistication + score: 7 + max: 8 + passed: true + comment: Custom diverging palette, conditional text colors, white cell borders, + light gray background — publication-quality + - id: DE-02 + name: Visual Refinement + score: 5 + max: 6 passed: true - comment: 'Grid removed for clean heatmap look, legend well placed; minor: - panel_grid_major=element_blank removes subtle structure' + comment: Grid removed, custom backgrounds, generous whitespace, spines absent + — highly polished + - id: DE-03 + name: Data Storytelling + score: 4 + max: 6 + passed: true + comment: Recovery narrative with extreme focal points, conditional text color + guides attention spec_compliance: - score: 25 - max: 25 + score: 15 + max: 15 items: - id: SC-01 name: Plot Type - score: 8 - max: 8 + score: 5 + max: 5 passed: true comment: Correct heatmap using geom_tile - id: SC-02 - name: Data Mapping - score: 5 - max: 5 + name: Required Features + score: 4 + max: 4 passed: true - comment: X=time periods, Y=regions, fill=value correctly assigned + comment: 'All spec features present: diverging colormap, annotations, colorbar, + logical ordering' - id: SC-03 - name: Required Features - score: 5 - max: 5 + name: Data Mapping + score: 3 + max: 3 passed: true - comment: Diverging colormap for +/- values ✓, value annotations ✓, colorbar - legend + comment: X=Quarter, Y=Department, fill=Growth (%) — correct - id: SC-04 - name: Data Range + name: Title & Legend score: 3 max: 3 passed: true - comment: All data visible, axes show complete range - - id: SC-05 - name: Legend Accuracy - score: 2 - max: 2 - passed: true - comment: '"Value" legend label correct with proper scale' - - id: SC-06 - name: Title Format - score: 2 - max: 2 - passed: true - comment: Uses exact format "heatmap-basic · plotnine · pyplots.ai" + comment: Title contains heatmap-basic · plotnine · pyplots.ai, legend labeled + Growth (%) data_quality: - score: 18 - max: 20 + score: 14 + max: 15 items: - id: DQ-01 name: Feature Coverage - score: 7 - max: 8 + score: 5 + max: 6 passed: true - comment: Shows both positive and negative values, trend patterns, regional - variation; could show more extreme outliers + comment: Shows positive/negative, near-zero, extremes, variation across both + dimensions - id: DQ-02 name: Realistic Context - score: 7 - max: 7 + score: 5 + max: 5 passed: true - comment: Performance metrics by region and quarter is a real, comprehensible - business scenario + comment: 'Realistic business scenario: quarterly departmental growth rates' - id: DQ-03 name: Appropriate Scale score: 4 - max: 5 + max: 4 passed: true - comment: Values range from ~-45 to ~+47, plausible for performance metrics - but units undefined + comment: Growth rates -32.5% to +38.2% are plausible code_quality: score: 10 max: 10 @@ -176,41 +185,47 @@ review: score: 3 max: 3 passed: true - comment: Clean imports → data → plot → save structure, no unnecessary functions/classes + comment: Clean imports → data → plot → save, no functions/classes - id: CQ-02 name: Reproducibility - score: 3 - max: 3 + score: 2 + max: 2 passed: true - comment: Uses np.random.seed(42) + comment: np.random.seed(42) - id: CQ-03 name: Clean Imports score: 2 max: 2 passed: true - comment: All imports are used + comment: All imports used - 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: Well-organized, vectorized data generation, appropriate complexity - id: CQ-05 - name: Output Correct + name: Output & API score: 1 max: 1 passed: true - comment: Saves as plot.png - library_features: - score: 2 - max: 5 + comment: Saves as plot.png, no deprecated functions + library_mastery: + score: 9 + max: 10 items: - - id: LF-01 - name: Uses distinctive library features - score: 2 + - id: LM-01 + name: Idiomatic Usage + score: 5 max: 5 - passed: false - comment: Good use of ggplot grammar with geom_tile + geom_text layering, but - scale_fill_gradient2 is basic; could leverage faceting or more advanced - theming + passed: true + comment: 'Idiomatic grammar of graphics: ggplot + aes + geom_tile + geom_text + + scales + theme' + - id: LM-02 + name: Distinctive Features + score: 4 + max: 5 + passed: true + comment: scale_fill_gradient2 with midpoint, scale_color_identity for conditional + text, pd.Categorical ordering verdict: APPROVED