|
| 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) |
0 commit comments