Skip to content

Commit 77390e5

Browse files
feat(plotnine): implement line-arrhenius (#5165)
## Implementation: `line-arrhenius` - plotnine Implements the **plotnine** version of `line-arrhenius`. **File:** `plots/line-arrhenius/implementations/plotnine.py` **Parent Issue:** #4408 --- :robot: *[impl-generate workflow](https://github.com/MarkusNeusinger/pyplots/actions/runs/23389777968)* --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
1 parent f330c0b commit 77390e5

2 files changed

Lines changed: 362 additions & 0 deletions

File tree

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
""" pyplots.ai
2+
line-arrhenius: Arrhenius Plot for Reaction Kinetics
3+
Library: plotnine 0.15.3 | Python 3.14.3
4+
Quality: 90/100 | Created: 2026-03-21
5+
"""
6+
7+
import numpy as np
8+
import pandas as pd
9+
from plotnine import (
10+
aes,
11+
annotate,
12+
element_blank,
13+
element_line,
14+
element_rect,
15+
element_text,
16+
geom_line,
17+
geom_point,
18+
geom_ribbon,
19+
ggplot,
20+
labs,
21+
scale_x_continuous,
22+
scale_y_continuous,
23+
theme,
24+
theme_minimal,
25+
)
26+
from scipy.stats import t as t_dist
27+
28+
29+
# Data — first-order decomposition with slight experimental scatter
30+
temperature_K = np.array([300, 350, 400, 450, 500, 550, 600])
31+
rate_constant_k = np.array([0.0013, 0.0091, 0.054, 0.19, 0.72, 1.75, 4.2])
32+
33+
inv_T = 1.0 / temperature_K
34+
ln_k = np.log(rate_constant_k)
35+
36+
# Compute regression statistics for annotations
37+
coeffs = np.polyfit(inv_T, ln_k, 1)
38+
slope, intercept = coeffs
39+
ln_k_pred = np.polyval(coeffs, inv_T)
40+
ss_res = np.sum((ln_k - ln_k_pred) ** 2)
41+
ss_tot = np.sum((ln_k - ln_k.mean()) ** 2)
42+
r_squared = 1 - ss_res / ss_tot
43+
44+
R = 8.314
45+
Ea_kJ = -slope * R / 1000
46+
47+
df = pd.DataFrame({"inv_T": inv_T, "ln_k": ln_k})
48+
49+
# Regression line data for manual ribbon + line (tighter control than geom_smooth)
50+
inv_T_fine = np.linspace(inv_T.min() - 0.0001, inv_T.max() + 0.0001, 200)
51+
ln_k_fit = np.polyval(coeffs, inv_T_fine)
52+
53+
# Compute residual SE for narrow confidence band
54+
n = len(inv_T)
55+
se_residual = np.sqrt(ss_res / (n - 2))
56+
inv_T_mean = inv_T.mean()
57+
inv_T_ss = np.sum((inv_T - inv_T_mean) ** 2)
58+
se_fit = se_residual * np.sqrt(1.0 / n + (inv_T_fine - inv_T_mean) ** 2 / inv_T_ss)
59+
t_val = t_dist.ppf(0.975, n - 2)
60+
ci_lower = ln_k_fit - t_val * se_fit
61+
ci_upper = ln_k_fit + t_val * se_fit
62+
63+
df_fit = pd.DataFrame({"inv_T": inv_T_fine, "ln_k_fit": ln_k_fit, "ci_lower": ci_lower, "ci_upper": ci_upper})
64+
65+
# Tick labels with temperature reference — select subset for clean spacing
66+
tick_temps = [300, 400, 500, 600]
67+
tick_positions = [1.0 / t for t in tick_temps]
68+
tick_labels = [f"{1.0 / t:.2e}\n({t} K)" for t in tick_temps]
69+
70+
# Annotation placement — upper-left region for better balance
71+
anno_x = inv_T.min() + 0.35 * (inv_T.max() - inv_T.min())
72+
anno_y_top = ln_k.max() - 0.15
73+
74+
# Combined annotation text block for polished typography
75+
anno_line1 = f"R² = {r_squared:.4f}"
76+
anno_line2 = f"Eₐ = {Ea_kJ:.1f} kJ/mol"
77+
anno_line3 = f"slope = −Eₐ/R = {slope:.0f} K"
78+
79+
# Plot — manual ribbon + line for tight CI, geom_point for markers
80+
plot = (
81+
ggplot(df, aes(x="inv_T", y="ln_k"))
82+
# Confidence ribbon — narrow band from manual calculation
83+
+ geom_ribbon(
84+
aes(x="inv_T", ymin="ci_lower", ymax="ci_upper"), data=df_fit, fill="#4a90d9", alpha=0.12, inherit_aes=False
85+
)
86+
# Regression line
87+
+ geom_line(aes(x="inv_T", y="ln_k_fit"), data=df_fit, color="#4a90d9", size=2.0, inherit_aes=False)
88+
# Data points with filled markers
89+
+ geom_point(color="#0d2240", fill="#306998", size=7, stroke=1.4, shape="o")
90+
+ scale_x_continuous(breaks=tick_positions, labels=tick_labels)
91+
+ scale_y_continuous(expand=(0.08, 0))
92+
# Annotation block — stacked text with visual hierarchy
93+
+ annotate(
94+
"label",
95+
x=anno_x,
96+
y=anno_y_top,
97+
label=anno_line1,
98+
size=17,
99+
color="#0d2240",
100+
fontweight="bold",
101+
ha="center",
102+
fill="#ffffff",
103+
alpha=0.75,
104+
label_padding=0.4,
105+
label_size=0,
106+
)
107+
+ annotate(
108+
"text", x=anno_x, y=anno_y_top - 0.9, label=anno_line2, size=15, color="#1a3a5c", fontweight="bold", ha="center"
109+
)
110+
+ annotate(
111+
"text",
112+
x=anno_x,
113+
y=anno_y_top - 1.65,
114+
label=anno_line3,
115+
size=13,
116+
color="#667788",
117+
fontstyle="italic",
118+
ha="center",
119+
)
120+
+ labs(x="1/T (K⁻¹)", y="ln(k)", title="line-arrhenius · plotnine · pyplots.ai")
121+
+ theme_minimal()
122+
+ theme(
123+
figure_size=(16, 9),
124+
plot_title=element_text(size=26, weight="bold", color="#0d2240", margin={"b": 15}),
125+
axis_title_x=element_text(size=20, color="#333333", margin={"t": 10}),
126+
axis_title_y=element_text(size=20, color="#333333", margin={"r": 10}),
127+
axis_text=element_text(size=16, color="#444444"),
128+
axis_ticks=element_line(color="#cccccc", size=0.5),
129+
plot_background=element_rect(fill="#f7f7f7", color="none"),
130+
panel_background=element_rect(fill="#f7f7f7", color="none"),
131+
panel_grid_major_x=element_blank(),
132+
panel_grid_minor=element_blank(),
133+
panel_grid_major_y=element_line(color="#dcdcdc", size=0.3, linetype="dashed"),
134+
plot_margin=0.05,
135+
)
136+
)
137+
138+
# Save
139+
plot.save("plot.png", dpi=300, verbose=False)
Lines changed: 223 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,223 @@
1+
library: plotnine
2+
specification_id: line-arrhenius
3+
created: '2026-03-21T22:06:52Z'
4+
updated: '2026-03-21T22:25:09Z'
5+
generated_by: claude-opus-4-5-20251101
6+
workflow_run: 23389777968
7+
issue: 4408
8+
python_version: 3.14.3
9+
library_version: 0.15.3
10+
preview_url: https://storage.googleapis.com/pyplots-images/plots/line-arrhenius/plotnine/plot.png
11+
preview_thumb: https://storage.googleapis.com/pyplots-images/plots/line-arrhenius/plotnine/plot_thumb.png
12+
preview_html: null
13+
quality_score: 90
14+
review:
15+
strengths:
16+
- Excellent spec compliance — all five required features present and well-executed
17+
- Strong visual hierarchy in annotations with typography differentiation (bold/italic)
18+
- Custom blue color palette with intentional hierarchy across all elements
19+
- Confidence ribbon adds statistical depth beyond minimum requirements
20+
- Dual x-axis labels (1/T + temperature in K) greatly aid interpretation
21+
- Clean, deterministic code with appropriate complexity
22+
weaknesses:
23+
- Minor empty space on right side of canvas between last two x-axis ticks
24+
- Data is extremely clean (R²=0.9968) — slightly more scatter would better showcase
25+
the regression fit
26+
image_description: 'The plot displays an Arrhenius plot with 1/T (K⁻¹) on the x-axis
27+
and ln(k) on the y-axis. Seven dark navy-blue circular data points are plotted
28+
along a medium-blue regression line (size 2.0) with a subtle light-blue confidence
29+
ribbon. The title "line-arrhenius · plotnine · pyplots.ai" appears in bold dark
30+
navy at the top. Three stacked annotations in the upper-left show: "R² = 0.9968"
31+
in a white semi-transparent label box (bold), "Eₐ = 40.6 kJ/mol" (bold, dark blue),
32+
and "slope = −Eₐ/R = -4878 K" (italic, gray). The x-axis has 4 tick labels showing
33+
both scientific notation and temperature in K (e.g., "1.67e-03 (600 K)"). Background
34+
is a warm light gray (#f7f7f7) with subtle dashed horizontal grid lines on the
35+
y-axis only. No x-axis grid. The overall design is clean and polished.'
36+
criteria_checklist:
37+
visual_quality:
38+
score: 29
39+
max: 30
40+
items:
41+
- id: VQ-01
42+
name: Text Legibility
43+
score: 8
44+
max: 8
45+
passed: true
46+
comment: 'All font sizes explicitly set: title 26pt, axis titles 20pt, tick
47+
text 16pt'
48+
- id: VQ-02
49+
name: No Overlap
50+
score: 6
51+
max: 6
52+
passed: true
53+
comment: No overlapping text, annotations well-spaced, 4 x-axis ticks avoids
54+
crowding
55+
- id: VQ-03
56+
name: Element Visibility
57+
score: 6
58+
max: 6
59+
passed: true
60+
comment: 7 data points at size=7 with stroke=1.4 clearly visible above regression
61+
line
62+
- id: VQ-04
63+
name: Color Accessibility
64+
score: 4
65+
max: 4
66+
passed: true
67+
comment: Single-series blue palette, no colorblind issues
68+
- id: VQ-05
69+
name: Layout & Canvas
70+
score: 3
71+
max: 4
72+
passed: true
73+
comment: Good 16:9 proportions, slight gap on right side
74+
- id: VQ-06
75+
name: Axis Labels & Title
76+
score: 2
77+
max: 2
78+
passed: true
79+
comment: 'Descriptive with units: 1/T (K⁻¹) and ln(k)'
80+
design_excellence:
81+
score: 15
82+
max: 20
83+
items:
84+
- id: DE-01
85+
name: Aesthetic Sophistication
86+
score: 6
87+
max: 8
88+
passed: true
89+
comment: Custom blue palette with intentional hierarchy, typography differentiation
90+
in annotations
91+
- id: DE-02
92+
name: Visual Refinement
93+
score: 5
94+
max: 6
95+
passed: true
96+
comment: 'theme_minimal customized: x-grid removed, y-grid subtle dashed,
97+
warm gray background'
98+
- id: DE-03
99+
name: Data Storytelling
100+
score: 4
101+
max: 6
102+
passed: true
103+
comment: Annotation hierarchy guides viewer from R² to Ea to slope, confidence
104+
band communicates fit quality
105+
spec_compliance:
106+
score: 15
107+
max: 15
108+
items:
109+
- id: SC-01
110+
name: Plot Type
111+
score: 5
112+
max: 5
113+
passed: true
114+
comment: 'Correct Arrhenius plot: ln(k) vs 1/T with linear regression'
115+
- id: SC-02
116+
name: Required Features
117+
score: 4
118+
max: 4
119+
passed: true
120+
comment: 'All spec features present: regression line, R², Ea/R slope, secondary
121+
temp labels, visible points'
122+
- id: SC-03
123+
name: Data Mapping
124+
score: 3
125+
max: 3
126+
passed: true
127+
comment: X=1/T, Y=ln(k), correctly assigned
128+
- id: SC-04
129+
name: Title & Legend
130+
score: 3
131+
max: 3
132+
passed: true
133+
comment: Title format correct, no legend needed for single series
134+
data_quality:
135+
score: 14
136+
max: 15
137+
items:
138+
- id: DQ-01
139+
name: Feature Coverage
140+
score: 5
141+
max: 6
142+
passed: true
143+
comment: 7 data points spanning 300-600K showing clear linear Arrhenius relationship
144+
- id: DQ-02
145+
name: Realistic Context
146+
score: 5
147+
max: 5
148+
passed: true
149+
comment: First-order decomposition reaction, genuine chemistry scenario
150+
- id: DQ-03
151+
name: Appropriate Scale
152+
score: 4
153+
max: 4
154+
passed: true
155+
comment: Temperature 300-600K, rate constants 0.0013-4.2, Ea=40.6 kJ/mol all
156+
realistic
157+
code_quality:
158+
score: 10
159+
max: 10
160+
items:
161+
- id: CQ-01
162+
name: KISS Structure
163+
score: 3
164+
max: 3
165+
passed: true
166+
comment: Clean imports-data-computation-plot-save flow, no functions/classes
167+
- id: CQ-02
168+
name: Reproducibility
169+
score: 2
170+
max: 2
171+
passed: true
172+
comment: Fully deterministic data, no random components
173+
- id: CQ-03
174+
name: Clean Imports
175+
score: 2
176+
max: 2
177+
passed: true
178+
comment: All imports used including scipy.stats.t for CI
179+
- id: CQ-04
180+
name: Code Elegance
181+
score: 2
182+
max: 2
183+
passed: true
184+
comment: Well-organized, appropriate complexity
185+
- id: CQ-05
186+
name: Output & API
187+
score: 1
188+
max: 1
189+
passed: true
190+
comment: Saves as plot.png at 300 DPI, current API
191+
library_mastery:
192+
score: 7
193+
max: 10
194+
items:
195+
- id: LM-01
196+
name: Idiomatic Usage
197+
score: 4
198+
max: 5
199+
passed: true
200+
comment: Good grammar-of-graphics layering with inherit_aes=False for separate
201+
data frames
202+
- id: LM-02
203+
name: Distinctive Features
204+
score: 3
205+
max: 5
206+
passed: true
207+
comment: Uses annotate label type, grammar-of-graphics composition, extensive
208+
theme customization
209+
verdict: APPROVED
210+
impl_tags:
211+
dependencies:
212+
- scipy
213+
techniques:
214+
- annotations
215+
- layer-composition
216+
- manual-ticks
217+
patterns:
218+
- data-generation
219+
dataprep:
220+
- regression
221+
styling:
222+
- alpha-blending
223+
- grid-styling

0 commit comments

Comments
 (0)