Skip to content

Commit b98610b

Browse files
feat(plotnine): implement scatter-marginal (#6130)
## Implementation: `scatter-marginal` - python/plotnine Implements the **python/plotnine** version of `scatter-marginal`. **File:** `plots/scatter-marginal/implementations/python/plotnine.py` **Parent Issue:** #2005 --- :robot: *[impl-generate workflow](https://github.com/MarkusNeusinger/anyplot/actions/runs/25592916886)* --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Markus Neusinger <2921697+MarkusNeusinger@users.noreply.github.com>
1 parent 622fa3b commit b98610b

2 files changed

Lines changed: 203 additions & 171 deletions

File tree

Lines changed: 53 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1-
""" pyplots.ai
1+
""" anyplot.ai
22
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
55
"""
66

7+
import os
8+
79
import numpy as np
810
import pandas as pd
911
from plotnine import (
@@ -25,95 +27,106 @@
2527
from plotnine.composition import plot_spacer
2628

2729

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
2941
np.random.seed(42)
3042
n = 200
31-
32-
# Study hours vs exam score with positive correlation
3343
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)
3746
exam_score = np.clip(exam_score, 30, 100)
38-
3947
df = pd.DataFrame({"study_hours": study_hours, "exam_score": exam_score})
4048

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
4850
main_w, main_h = 12, 6.5
4951
marg_w, marg_h = 4, 2.5
5052

51-
# Common theme elements for large canvas
53+
# Shared theme - L-shaped spine (left + bottom only)
5254
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),
5858
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}),
6065
)
6166

6267
# Top histogram (x distribution)
6368
top_hist = (
6469
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")
6873
+ base_theme
6974
+ theme(
7075
figure_size=(main_w, marg_h),
7176
axis_text_x=element_blank(),
7277
axis_ticks_major_x=element_blank(),
78+
axis_ticks_minor_x=element_blank(),
79+
axis_line_x=element_blank(),
7380
axis_title_y=element_blank(),
7481
axis_text_y=element_blank(),
7582
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(),
7686
)
7787
)
7888

79-
# Right histogram (y distribution, flipped)
89+
# Right histogram (y distribution)
8090
right_hist = (
8191
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)
8393
+ coord_flip()
84-
+ scale_x_continuous(limits=(y_min, y_max))
94+
+ scale_x_continuous(limits=(30, 105))
8595
+ labs(x="", y="")
8696
+ base_theme
8797
+ theme(
8898
figure_size=(marg_w, main_h),
8999
axis_text_y=element_blank(),
90100
axis_ticks_major_y=element_blank(),
101+
axis_ticks_minor_y=element_blank(),
102+
axis_line_y=element_blank(),
91103
axis_title_x=element_blank(),
92104
axis_text_x=element_blank(),
93105
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(),
94109
)
95110
)
96111

97112
# Main scatter plot
98113
scatter_plot = (
99114
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))
103118
+ labs(x="Study Hours per Week", y="Exam Score (%)")
104119
+ base_theme
105120
+ theme(figure_size=(main_w, main_h))
106121
)
107122

108-
# Empty spacer for top-right corner
123+
# Spacer
109124
spacer = plot_spacer() + theme(figure_size=(marg_w, marg_h))
110125

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)
115128

116-
# Draw to matplotlib figure and save with correct dimensions
129+
# Save
117130
fig = composed.draw()
118131
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

Comments
 (0)