|
1 | | -""" pyplots.ai |
| 1 | +""" anyplot.ai |
2 | 2 | scatter-marginal: Scatter Plot with Marginal Distributions |
3 | | -Library: plotnine 0.15.2 | Python 3.13.11 |
4 | | -Quality: 91/100 | Created: 2025-12-26 |
| 3 | +Library: plotnine 0.15.4 | Python 3.13.13 |
| 4 | +Quality: 93/100 | Updated: 2026-05-09 |
5 | 5 | """ |
6 | 6 |
|
| 7 | +import os |
| 8 | + |
7 | 9 | import numpy as np |
8 | 10 | import pandas as pd |
9 | 11 | from plotnine import ( |
|
25 | 27 | from plotnine.composition import plot_spacer |
26 | 28 |
|
27 | 29 |
|
28 | | -# Data - Bivariate data with moderate correlation (realistic scenario) |
| 30 | +# Theme tokens |
| 31 | +THEME = os.getenv("ANYPLOT_THEME", "light") |
| 32 | +PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17" |
| 33 | +INK = "#1A1A17" if THEME == "light" else "#F0EFE8" |
| 34 | +INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0" |
| 35 | + |
| 36 | +# Okabe-Ito palette |
| 37 | +SCATTER_COLOR = "#009E73" # Brand green (position 1) |
| 38 | +MARGINAL_COLOR = "#D55E00" # Vermillion (position 2) |
| 39 | + |
| 40 | +# Data - Bivariate data with correlation |
29 | 41 | np.random.seed(42) |
30 | 42 | n = 200 |
31 | | - |
32 | | -# Study hours vs exam score with positive correlation |
33 | 43 | study_hours = np.random.normal(25, 8, n) |
34 | | -study_hours = np.clip(study_hours, 5, 45) # Realistic range |
35 | | -noise = np.random.normal(0, 8, n) |
36 | | -exam_score = 35 + 1.5 * study_hours + noise |
| 44 | +study_hours = np.clip(study_hours, 5, 45) |
| 45 | +exam_score = 35 + 1.5 * study_hours + np.random.normal(0, 8, n) |
37 | 46 | exam_score = np.clip(exam_score, 30, 100) |
38 | | - |
39 | 47 | df = pd.DataFrame({"study_hours": study_hours, "exam_score": exam_score}) |
40 | 48 |
|
41 | | -# Shared axis limits (include all data with margin) |
42 | | -x_min, x_max = 0, 50 |
43 | | -y_min, y_max = 30, 105 |
44 | | - |
45 | | -# Figure sizes calculated for 4800x2700 final output at 300 DPI |
46 | | -# Total: 16x9 inches = 4800x2700 px |
47 | | -# Layout: scatter (12x6.5), top hist (12x2.5), right hist (4x6.5), spacer (4x2.5) |
| 49 | +# Layout dimensions for 4800x2700 output |
48 | 50 | main_w, main_h = 12, 6.5 |
49 | 51 | marg_w, marg_h = 4, 2.5 |
50 | 52 |
|
51 | | -# Common theme elements for large canvas |
| 53 | +# Shared theme - L-shaped spine (left + bottom only) |
52 | 54 | base_theme = theme_minimal() + theme( |
53 | | - text=element_text(size=14), |
54 | | - axis_title=element_text(size=20), |
55 | | - axis_text=element_text(size=16), |
56 | | - plot_title=element_text(size=24), |
57 | | - panel_grid_major=element_line(color="#cccccc", size=0.5, alpha=0.3), |
| 55 | + plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG), |
| 56 | + panel_background=element_rect(fill=PAGE_BG, color=PAGE_BG), |
| 57 | + panel_grid_major=element_line(color=INK, size=0.3, alpha=0.10), |
58 | 58 | panel_grid_minor=element_blank(), |
59 | | - plot_background=element_rect(fill="white"), |
| 59 | + panel_border=element_blank(), |
| 60 | + axis_title=element_text(color=INK, size=20, weight="bold"), |
| 61 | + axis_text=element_text(color=INK_SOFT, size=16), |
| 62 | + axis_line=element_line(color=INK_SOFT, size=0.5), |
| 63 | + axis_ticks=element_line(color=INK_SOFT, size=0.4), |
| 64 | + plot_title=element_text(color=INK, size=24, weight="bold", margin={"t": 10, "b": 10}), |
60 | 65 | ) |
61 | 66 |
|
62 | 67 | # Top histogram (x distribution) |
63 | 68 | top_hist = ( |
64 | 69 | ggplot(df, aes(x="study_hours")) |
65 | | - + geom_histogram(bins=15, fill="#306998", color="#1a3d5c", alpha=0.7, size=0.3) |
66 | | - + scale_x_continuous(limits=(x_min, x_max)) |
67 | | - + labs(x="", y="", title="scatter-marginal · plotnine · pyplots.ai") |
| 70 | + + geom_histogram(bins=15, fill=MARGINAL_COLOR, color=INK_SOFT, alpha=0.7, size=0.3) |
| 71 | + + scale_x_continuous(limits=(0, 50)) |
| 72 | + + labs(x="", y="", title="scatter-marginal · plotnine · anyplot.ai") |
68 | 73 | + base_theme |
69 | 74 | + theme( |
70 | 75 | figure_size=(main_w, marg_h), |
71 | 76 | axis_text_x=element_blank(), |
72 | 77 | axis_ticks_major_x=element_blank(), |
| 78 | + axis_ticks_minor_x=element_blank(), |
| 79 | + axis_line_x=element_blank(), |
73 | 80 | axis_title_y=element_blank(), |
74 | 81 | axis_text_y=element_blank(), |
75 | 82 | axis_ticks_major_y=element_blank(), |
| 83 | + axis_ticks_minor_y=element_blank(), |
| 84 | + axis_line_y=element_blank(), |
| 85 | + panel_grid_major=element_blank(), |
76 | 86 | ) |
77 | 87 | ) |
78 | 88 |
|
79 | | -# Right histogram (y distribution, flipped) |
| 89 | +# Right histogram (y distribution) |
80 | 90 | right_hist = ( |
81 | 91 | ggplot(df, aes(x="exam_score")) |
82 | | - + geom_histogram(bins=15, fill="#FFD43B", color="#d4a80a", alpha=0.7, size=0.3) |
| 92 | + + geom_histogram(bins=15, fill=MARGINAL_COLOR, color=INK_SOFT, alpha=0.7, size=0.3) |
83 | 93 | + coord_flip() |
84 | | - + scale_x_continuous(limits=(y_min, y_max)) |
| 94 | + + scale_x_continuous(limits=(30, 105)) |
85 | 95 | + labs(x="", y="") |
86 | 96 | + base_theme |
87 | 97 | + theme( |
88 | 98 | figure_size=(marg_w, main_h), |
89 | 99 | axis_text_y=element_blank(), |
90 | 100 | axis_ticks_major_y=element_blank(), |
| 101 | + axis_ticks_minor_y=element_blank(), |
| 102 | + axis_line_y=element_blank(), |
91 | 103 | axis_title_x=element_blank(), |
92 | 104 | axis_text_x=element_blank(), |
93 | 105 | axis_ticks_major_x=element_blank(), |
| 106 | + axis_ticks_minor_x=element_blank(), |
| 107 | + axis_line_x=element_blank(), |
| 108 | + panel_grid_major=element_blank(), |
94 | 109 | ) |
95 | 110 | ) |
96 | 111 |
|
97 | 112 | # Main scatter plot |
98 | 113 | scatter_plot = ( |
99 | 114 | ggplot(df, aes(x="study_hours", y="exam_score")) |
100 | | - + geom_point(size=3.5, alpha=0.6, color="#306998") |
101 | | - + scale_x_continuous(limits=(x_min, x_max)) |
102 | | - + scale_y_continuous(limits=(y_min, y_max)) |
| 115 | + + geom_point(size=3.5, alpha=0.6, color=SCATTER_COLOR) |
| 116 | + + scale_x_continuous(limits=(0, 50)) |
| 117 | + + scale_y_continuous(limits=(30, 105)) |
103 | 118 | + labs(x="Study Hours per Week", y="Exam Score (%)") |
104 | 119 | + base_theme |
105 | 120 | + theme(figure_size=(main_w, main_h)) |
106 | 121 | ) |
107 | 122 |
|
108 | | -# Empty spacer for top-right corner |
| 123 | +# Spacer |
109 | 124 | spacer = plot_spacer() + theme(figure_size=(marg_w, marg_h)) |
110 | 125 |
|
111 | | -# Compose: top row (histogram | spacer), bottom row (scatter | right histogram) |
112 | | -top_row = top_hist | spacer |
113 | | -bottom_row = scatter_plot | right_hist |
114 | | -composed = top_row / bottom_row |
| 126 | +# Compose layout |
| 127 | +composed = (top_hist | spacer) / (scatter_plot | right_hist) |
115 | 128 |
|
116 | | -# Draw to matplotlib figure and save with correct dimensions |
| 129 | +# Save |
117 | 130 | fig = composed.draw() |
118 | 131 | fig.set_size_inches(16, 9) |
119 | | -fig.savefig("plot.png", dpi=300, bbox_inches="tight", facecolor="white") |
| 132 | +fig.savefig(f"plot-{THEME}.png", dpi=300, bbox_inches="tight", facecolor=PAGE_BG) |
0 commit comments