Skip to content

Commit 2b06e36

Browse files
feat(plotnine): implement subplot-mosaic (#3066)
## Implementation: `subplot-mosaic` - plotnine Implements the **plotnine** version of `subplot-mosaic`. **File:** `plots/subplot-mosaic/implementations/plotnine.py` **Parent Issue:** #3002 --- :robot: *[impl-generate workflow](https://github.com/MarkusNeusinger/pyplots/actions/runs/20617510969)* --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
1 parent dd61ceb commit 2b06e36

2 files changed

Lines changed: 195 additions & 0 deletions

File tree

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
""" pyplots.ai
2+
subplot-mosaic: Mosaic Subplot Layout with Varying Sizes
3+
Library: plotnine 0.15.2 | Python 3.13.11
4+
Quality: 90/100 | Created: 2025-12-31
5+
"""
6+
7+
import matplotlib.pyplot as plt
8+
import numpy as np
9+
import pandas as pd
10+
from plotnine import (
11+
aes,
12+
element_line,
13+
element_rect,
14+
element_text,
15+
geom_bar,
16+
geom_line,
17+
geom_point,
18+
geom_text,
19+
geom_tile,
20+
ggplot,
21+
labs,
22+
scale_color_manual,
23+
scale_fill_gradient2,
24+
scale_fill_manual,
25+
theme,
26+
theme_minimal,
27+
)
28+
29+
30+
# Data - Product performance dashboard
31+
np.random.seed(42)
32+
33+
# Daily metrics for overview chart (Panel A - large top panel)
34+
n_days = 60
35+
days = np.arange(n_days)
36+
products = ["Alpha", "Beta", "Gamma"]
37+
colors = ["#306998", "#FFD43B", "#5DADE2"]
38+
39+
df_overview = pd.concat(
40+
[
41+
pd.DataFrame({"day": days, "sales": 100 + i * 15 + np.cumsum(np.random.randn(n_days) * 3), "product": name})
42+
for i, name in enumerate(products)
43+
],
44+
ignore_index=True,
45+
)
46+
47+
# Category performance (Panel B - medium right panel)
48+
categories = ["Q1", "Q2", "Q3", "Q4"]
49+
revenues = [48, 35, 42, 55]
50+
df_category = pd.DataFrame({"quarter": categories, "revenue": revenues})
51+
df_category["quarter"] = pd.Categorical(df_category["quarter"], categories=categories, ordered=True)
52+
53+
# Distribution data (Panel C - bottom left)
54+
df_scatter = pd.DataFrame({"units": np.random.uniform(50, 400, 80), "margin": 15 + np.random.randn(80) * 8})
55+
df_scatter["margin"] = df_scatter["margin"] + 0.03 * df_scatter["units"]
56+
57+
# Heatmap data (Panel D - bottom middle)
58+
regions = ["North", "South", "East", "West"]
59+
metrics_list = ["Sales", "Profit", "Growth"]
60+
heatmap_vals = np.random.rand(len(metrics_list), len(regions)) * 100
61+
df_heat = pd.DataFrame(
62+
[
63+
{"region": regions[j], "metric": metrics_list[i], "value": heatmap_vals[i, j]}
64+
for i in range(len(metrics_list))
65+
for j in range(len(regions))
66+
]
67+
)
68+
df_heat["region"] = pd.Categorical(df_heat["region"], categories=regions, ordered=True)
69+
df_heat["metric"] = pd.Categorical(df_heat["metric"], categories=metrics_list[::-1], ordered=True)
70+
71+
# Small metric panel (Panel E - bottom right)
72+
months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun"]
73+
monthly_perf = [82, 78, 91, 88, 95, 92]
74+
df_monthly = pd.DataFrame({"month": months, "score": monthly_perf})
75+
df_monthly["month"] = pd.Categorical(df_monthly["month"], categories=months, ordered=True)
76+
77+
# Create all plotnine plots with proper theming for 4800x2700 output
78+
base_theme = theme_minimal() + theme(
79+
plot_title=element_text(size=22, face="bold", ha="center"),
80+
axis_title=element_text(size=18),
81+
axis_text=element_text(size=15),
82+
legend_text=element_text(size=14),
83+
legend_title=element_text(size=16, face="bold"),
84+
panel_grid_major=element_line(color="#cccccc", alpha=0.3),
85+
panel_grid_minor=element_line(alpha=0),
86+
panel_background=element_rect(fill="white"),
87+
)
88+
89+
# Panel A: Sales Overview - plotnine line + point plot (LARGE - 2/3 width)
90+
p_overview = (
91+
ggplot(df_overview, aes(x="day", y="sales", color="product"))
92+
+ geom_line(size=1.5, alpha=0.9)
93+
+ geom_point(size=2.5, alpha=0.6)
94+
+ scale_color_manual(values=colors)
95+
+ labs(x="Day", y="Sales (Units)", color="Product")
96+
+ base_theme
97+
+ theme(legend_position="right", figure_size=(12, 5.5))
98+
)
99+
100+
# Panel B: Quarterly Revenue - plotnine bar plot (SMALL - 1/3 width)
101+
p_category = (
102+
ggplot(df_category, aes(x="quarter", y="revenue", fill="quarter"))
103+
+ geom_bar(stat="identity", width=0.7, show_legend=False)
104+
+ scale_fill_manual(values=["#306998", "#4A8BBF", "#6BA3D6", "#FFD43B"])
105+
+ labs(x="Quarter", y="Revenue (k$)")
106+
+ base_theme
107+
+ theme(figure_size=(4, 5.5))
108+
)
109+
110+
# Panel C: Units vs Margin - plotnine scatter plot
111+
p_scatter = (
112+
ggplot(df_scatter, aes(x="units", y="margin"))
113+
+ geom_point(size=3.5, color="#306998", alpha=0.7)
114+
+ labs(x="Units Sold", y="Margin (%)")
115+
+ base_theme
116+
+ theme(figure_size=(5, 3.5))
117+
)
118+
119+
# Panel D: Regional Performance - plotnine heatmap with geom_tile
120+
p_heatmap = (
121+
ggplot(df_heat, aes(x="region", y="metric", fill="value"))
122+
+ geom_tile(color="white", size=1)
123+
+ geom_text(aes(label="value"), format_string="{:.0f}", size=10, color="white")
124+
+ scale_fill_gradient2(low="#FFD43B", mid="#5DADE2", high="#306998", midpoint=50)
125+
+ labs(x="Region", y="", fill="Score")
126+
+ base_theme
127+
+ theme(legend_position="bottom", legend_direction="horizontal", figure_size=(5.5, 3.5))
128+
)
129+
130+
# Panel E: Monthly Score - plotnine bar plot
131+
p_monthly = (
132+
ggplot(df_monthly, aes(x="month", y="score"))
133+
+ geom_bar(stat="identity", fill="#306998", width=0.6)
134+
+ labs(x="Month", y="Score")
135+
+ base_theme
136+
+ theme(figure_size=(5, 3.5))
137+
)
138+
139+
# Use plotnine's composition to create a mosaic-like layout
140+
# The figure_size differences create visual hierarchy:
141+
# Panel A (12x5.5) vs Panel B (4x5.5) = ~3:1 width ratio in top row
142+
# This approximates the mosaic pattern where A spans more than B
143+
144+
# Create the composition
145+
# Top section: overview (large ~3/4) | category (small ~1/4)
146+
# Bottom section: scatter | heatmap | monthly (equal thirds)
147+
top_row = p_overview | p_category
148+
bottom_row = p_scatter | p_heatmap | p_monthly
149+
full_layout = top_row / bottom_row
150+
151+
# Draw the composed layout
152+
fig = full_layout.draw()
153+
154+
# Adjust figure size for 4800x2700 target
155+
fig.set_size_inches(16, 9)
156+
157+
# Adjust spacing - tighter wspace to keep heatmap legend near panel
158+
fig.subplots_adjust(top=0.88, bottom=0.10, left=0.05, right=0.95, hspace=0.30, wspace=0.18)
159+
160+
# Add main figure title using suptitle
161+
fig.suptitle("subplot-mosaic · plotnine · pyplots.ai", fontsize=32, fontweight="bold", y=0.97)
162+
163+
# Save
164+
fig.savefig("plot.png", dpi=300, bbox_inches="tight", facecolor="white")
165+
plt.close(fig)
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
library: plotnine
2+
specification_id: subplot-mosaic
3+
created: '2025-12-31T11:20:28Z'
4+
updated: '2025-12-31T11:30:45Z'
5+
generated_by: claude-opus-4-5-20251101
6+
workflow_run: 20617510969
7+
issue: 3002
8+
python_version: 3.13.11
9+
library_version: 0.15.2
10+
preview_url: https://storage.googleapis.com/pyplots-images/plots/subplot-mosaic/plotnine/plot.png
11+
preview_thumb: https://storage.googleapis.com/pyplots-images/plots/subplot-mosaic/plotnine/plot_thumb.png
12+
preview_html: null
13+
quality_score: 90
14+
review:
15+
strengths:
16+
- Excellent use of plotnine composition operators (| and /) to create mosaic-like
17+
layout
18+
- Clean, consistent color scheme using blue/yellow palette throughout all panels
19+
- Good visual hierarchy with larger overview panel and smaller detail panels
20+
- Well-structured code with clear separation of data generation and plotting
21+
- Appropriate use of diverse geoms (geom_line, geom_point, geom_bar, geom_tile,
22+
geom_text)
23+
- Professional theming with customized text sizes for high-resolution output
24+
weaknesses:
25+
- Heatmap colorbar legend is placed at the very bottom of the figure, visually separated
26+
from its panel
27+
- Title uses suptitle which works but could be more integrated with plotnine native
28+
theme system
29+
- Could demonstrate the mosaic empty cell feature mentioned in the spec using placeholder
30+
characters

0 commit comments

Comments
 (0)