|
| 1 | +""" pyplots.ai |
| 2 | +subplot-grid: Subplot Grid Layout |
| 3 | +Library: plotnine 0.15.2 | Python 3.13.11 |
| 4 | +Quality: 91/100 | Created: 2025-12-30 |
| 5 | +""" |
| 6 | + |
| 7 | +import numpy as np |
| 8 | +import pandas as pd |
| 9 | +from plotnine import ( |
| 10 | + aes, |
| 11 | + element_line, |
| 12 | + element_rect, |
| 13 | + element_text, |
| 14 | + geom_bar, |
| 15 | + geom_histogram, |
| 16 | + geom_line, |
| 17 | + geom_point, |
| 18 | + ggplot, |
| 19 | + labs, |
| 20 | + scale_color_manual, |
| 21 | + scale_fill_manual, |
| 22 | + scale_x_continuous, |
| 23 | + stat_smooth, |
| 24 | + theme, |
| 25 | + theme_minimal, |
| 26 | +) |
| 27 | + |
| 28 | + |
| 29 | +# Data - Product performance dashboard |
| 30 | +np.random.seed(42) |
| 31 | + |
| 32 | +# Daily product metrics |
| 33 | +n_days = 40 |
| 34 | +days = pd.date_range("2024-01-01", periods=n_days, freq="D") |
| 35 | +products = ["A", "B"] |
| 36 | + |
| 37 | +# Generate time series data |
| 38 | +data_list = [] |
| 39 | +for product in products: |
| 40 | + base = 100 if product == "A" else 85 |
| 41 | + trend = 0.5 if product == "A" else 0.8 |
| 42 | + sales = base + np.arange(n_days) * trend + np.random.randn(n_days) * 10 |
| 43 | + data_list.append(pd.DataFrame({"date": days, "sales": sales, "product": product})) |
| 44 | + |
| 45 | +df_timeseries = pd.concat(data_list, ignore_index=True) |
| 46 | +df_timeseries["day_num"] = (df_timeseries["date"] - df_timeseries["date"].min()).dt.days |
| 47 | + |
| 48 | +# Category breakdown data |
| 49 | +categories = ["Q1", "Q2", "Q3", "Q4"] |
| 50 | +revenues = [45, 32, 28, 18] |
| 51 | +df_category = pd.DataFrame({"category": categories, "revenue": revenues}) |
| 52 | +df_category["category"] = pd.Categorical(df_category["category"], categories=categories, ordered=True) |
| 53 | + |
| 54 | +# Product distribution data |
| 55 | +df_prod_a = df_timeseries[df_timeseries["product"] == "A"]["sales"] |
| 56 | + |
| 57 | +# Scatter data - relationship between units sold and profit margin |
| 58 | +units = np.random.uniform(100, 500, 60) |
| 59 | +margin = 20 + 0.03 * units + np.random.randn(60) * 5 |
| 60 | +df_scatter = pd.DataFrame({"units": units, "margin": margin}) |
| 61 | + |
| 62 | +# Define colors |
| 63 | +colors = ["#306998", "#FFD43B"] |
| 64 | + |
| 65 | +# Shared theme for all plots - sized for 4800x2700 canvas |
| 66 | +base_theme = theme_minimal() + theme( |
| 67 | + plot_title=element_text(size=22, face="bold", ha="center", margin={"b": 15}), |
| 68 | + axis_title=element_text(size=18), |
| 69 | + axis_text=element_text(size=14), |
| 70 | + legend_text=element_text(size=14), |
| 71 | + legend_title=element_text(size=16, face="bold"), |
| 72 | + panel_grid_major=element_line(color="#cccccc", alpha=0.3), |
| 73 | + panel_grid_minor=element_line(color="#eeeeee", alpha=0.2), |
| 74 | + panel_background=element_rect(fill="white"), |
| 75 | + plot_margin=0.02, |
| 76 | +) |
| 77 | + |
| 78 | +# Plot 1: Sales trend over time (Line chart) |
| 79 | +p1 = ( |
| 80 | + ggplot(df_timeseries, aes(x="day_num", y="sales", color="product")) |
| 81 | + + geom_line(size=1.5) |
| 82 | + + geom_point(size=3, alpha=0.7) |
| 83 | + + stat_smooth(method="lm", se=False, linetype="dashed", size=1.0) |
| 84 | + + scale_color_manual(values=colors) |
| 85 | + + labs(title="Sales Trend", x="Day", y="Sales (Units)", color="Product") |
| 86 | + + base_theme |
| 87 | + + theme(legend_position="right") |
| 88 | +) |
| 89 | + |
| 90 | +# Plot 2: Revenue by category (Bar chart) |
| 91 | +p2 = ( |
| 92 | + ggplot(df_category, aes(x="category", y="revenue", fill="category")) |
| 93 | + + geom_bar(stat="identity", width=0.7, show_legend=False) |
| 94 | + + scale_fill_manual(values=["#306998", "#4A8BBF", "#6BA3D6", "#FFD43B"]) |
| 95 | + + labs(title="Quarterly Revenue", x="Quarter", y="Revenue (k$)") |
| 96 | + + base_theme |
| 97 | +) |
| 98 | + |
| 99 | +# Plot 3: Sales distribution histogram for Product A |
| 100 | +df_hist = pd.DataFrame({"sales": df_prod_a.values}) |
| 101 | +p3 = ( |
| 102 | + ggplot(df_hist, aes(x="sales")) |
| 103 | + + geom_histogram(bins=10, fill="#306998", color="white", alpha=0.8) |
| 104 | + + scale_x_continuous(breaks=[90, 105, 120]) |
| 105 | + + labs(title="Sales Distribution (Product A)", x="Sales (Units)", y="Frequency") |
| 106 | + + base_theme |
| 107 | +) |
| 108 | + |
| 109 | +# Plot 4: Units vs Margin scatter plot |
| 110 | +p4 = ( |
| 111 | + ggplot(df_scatter, aes(x="units", y="margin")) |
| 112 | + + geom_point(size=4, color="#306998", alpha=0.7) |
| 113 | + + stat_smooth(method="lm", color="#FFD43B", se=True, fill="#FFD43B", alpha=0.2) |
| 114 | + + labs(title="Units vs Margin", x="Units Sold", y="Profit Margin (%)") |
| 115 | + + base_theme |
| 116 | +) |
| 117 | + |
| 118 | +# Compose into 2x2 grid using plotnine's composition operators |
| 119 | +# | = beside (columns), / = stack (rows) |
| 120 | +top_row = p1 | p2 |
| 121 | +bottom_row = p3 | p4 |
| 122 | +grid = top_row / bottom_row |
| 123 | + |
| 124 | +# Draw the grid and add a main figure title using matplotlib's suptitle |
| 125 | +fig = grid.draw() |
| 126 | +# Resize figure with extra height for the main title (16:9 base + title space) |
| 127 | +fig.set_size_inches(16, 11) |
| 128 | +# Compress subplots significantly to make room for the main title at the top |
| 129 | +fig.subplots_adjust(top=0.76, bottom=0.08, hspace=0.40, wspace=0.25) |
| 130 | +# Add main title using suptitle positioned well above all subplot titles |
| 131 | +fig.suptitle("subplot-grid · plotnine · pyplots.ai", fontsize=28, fontweight="bold", y=0.90) |
| 132 | +fig.savefig("plot.png", dpi=300, bbox_inches="tight") |
0 commit comments