Skip to content

Commit 2fa3fdc

Browse files
feat(plotly): implement line-reaction-coordinate (#5150)
## Implementation: `line-reaction-coordinate` - plotly Implements the **plotly** version of `line-reaction-coordinate`. **File:** `plots/line-reaction-coordinate/implementations/plotly.py` **Parent Issue:** #4409 --- :robot: *[impl-generate workflow](https://github.com/MarkusNeusinger/pyplots/actions/runs/23388404657)* --------- 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 5248cb5 commit 2fa3fdc

2 files changed

Lines changed: 463 additions & 0 deletions

File tree

Lines changed: 244 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,244 @@
1+
""" pyplots.ai
2+
line-reaction-coordinate: Reaction Coordinate Energy Diagram
3+
Library: plotly 6.6.0 | Python 3.14.3
4+
Quality: 91/100 | Created: 2026-03-21
5+
"""
6+
7+
import numpy as np
8+
import plotly.graph_objects as go
9+
10+
11+
# Data
12+
reactant_energy = 50.0
13+
transition_energy = 120.0
14+
product_energy = 20.0
15+
peak_pos = 0.4
16+
17+
reaction_coord = np.linspace(0, 1, 500)
18+
19+
baseline = reactant_energy + (product_energy - reactant_energy) * (3 * reaction_coord**2 - 2 * reaction_coord**3)
20+
21+
barrier_height = transition_energy - (
22+
reactant_energy + (product_energy - reactant_energy) * (3 * peak_pos**2 - 2 * peak_pos**3)
23+
)
24+
gaussian_bump = barrier_height * np.exp(-((reaction_coord - peak_pos) ** 2) / (2 * 0.018))
25+
26+
energy = baseline + gaussian_bump
27+
28+
# Colorblind-safe colors: blue for Ea, orange for ΔH
29+
ea_color = "#0077BB"
30+
dh_color = "#EE7733"
31+
32+
# Plot
33+
fig = go.Figure()
34+
35+
# Shaded region under curve for visual polish
36+
fig.add_trace(
37+
go.Scatter(
38+
x=reaction_coord,
39+
y=energy,
40+
mode="lines",
41+
line={"color": "rgba(0,0,0,0)", "width": 0},
42+
fill="tozeroy",
43+
fillcolor="rgba(48,105,152,0.06)",
44+
showlegend=False,
45+
hoverinfo="skip",
46+
)
47+
)
48+
49+
fig.add_trace(
50+
go.Scatter(
51+
x=reaction_coord,
52+
y=energy,
53+
mode="lines",
54+
line={"color": "#306998", "width": 4, "shape": "spline"},
55+
showlegend=False,
56+
hovertemplate=("Reaction Coordinate: %{x:.2f}<br>Energy: %{y:.1f} kJ/mol<extra></extra>"),
57+
)
58+
)
59+
60+
# Horizontal dashed lines at reactant and product energy levels
61+
for x0, x1, y_level in [(-0.05, 0.28, reactant_energy), (0.72, 1.05, product_energy)]:
62+
fig.add_shape(
63+
type="line", x0=x0, x1=x1, y0=y_level, y1=y_level, line={"color": "#BBBBBB", "width": 1.5, "dash": "dash"}
64+
)
65+
66+
# Extended dashed line at reactant level on ΔH side for reference
67+
fig.add_shape(
68+
type="line",
69+
x0=0.82,
70+
x1=0.94,
71+
y0=reactant_energy,
72+
y1=reactant_energy,
73+
line={"color": "#BBBBBB", "width": 1, "dash": "dot"},
74+
)
75+
76+
# Activation energy (Ea) double-headed arrow
77+
ea_x = 0.14
78+
fig.add_shape(
79+
type="line", x0=ea_x, y0=reactant_energy, x1=ea_x, y1=transition_energy, line={"color": ea_color, "width": 2.5}
80+
)
81+
fig.add_annotation(
82+
x=ea_x,
83+
y=transition_energy,
84+
ax=0,
85+
ay=-14,
86+
text="",
87+
showarrow=True,
88+
arrowhead=2,
89+
arrowsize=1.5,
90+
arrowwidth=2.5,
91+
arrowcolor=ea_color,
92+
)
93+
fig.add_annotation(
94+
x=ea_x,
95+
y=reactant_energy,
96+
ax=0,
97+
ay=14,
98+
text="",
99+
showarrow=True,
100+
arrowhead=2,
101+
arrowsize=1.5,
102+
arrowwidth=2.5,
103+
arrowcolor=ea_color,
104+
)
105+
fig.add_annotation(
106+
x=ea_x,
107+
y=(reactant_energy + transition_energy) / 2,
108+
text="E<sub>a</sub> = 70 kJ/mol",
109+
showarrow=False,
110+
xanchor="right",
111+
xshift=-14,
112+
font={"size": 19, "color": ea_color, "family": "Arial Black, sans-serif"},
113+
)
114+
115+
# Horizontal dashed line at transition state level (for Ea reference)
116+
fig.add_shape(
117+
type="line",
118+
x0=ea_x - 0.02,
119+
x1=peak_pos + 0.08,
120+
y0=transition_energy,
121+
y1=transition_energy,
122+
line={"color": "#BBBBBB", "width": 1, "dash": "dot"},
123+
)
124+
125+
# Enthalpy change (ΔH) double-headed arrow
126+
dh_x = 0.88
127+
fig.add_shape(
128+
type="line", x0=dh_x, y0=product_energy, x1=dh_x, y1=reactant_energy, line={"color": dh_color, "width": 2.5}
129+
)
130+
fig.add_annotation(
131+
x=dh_x,
132+
y=reactant_energy,
133+
ax=0,
134+
ay=-14,
135+
text="",
136+
showarrow=True,
137+
arrowhead=2,
138+
arrowsize=1.5,
139+
arrowwidth=2.5,
140+
arrowcolor=dh_color,
141+
)
142+
fig.add_annotation(
143+
x=dh_x,
144+
y=product_energy,
145+
ax=0,
146+
ay=14,
147+
text="",
148+
showarrow=True,
149+
arrowhead=2,
150+
arrowsize=1.5,
151+
arrowwidth=2.5,
152+
arrowcolor=dh_color,
153+
)
154+
fig.add_annotation(
155+
x=dh_x,
156+
y=(reactant_energy + product_energy) / 2,
157+
text="ΔH = −30 kJ/mol",
158+
showarrow=False,
159+
xanchor="left",
160+
xshift=14,
161+
font={"size": 19, "color": dh_color, "family": "Arial Black, sans-serif"},
162+
)
163+
164+
# Labels — positioned to avoid crowding with arrows
165+
fig.add_annotation(
166+
x=0.02,
167+
y=reactant_energy,
168+
text="<b>Reactants</b><br>50 kJ/mol",
169+
showarrow=False,
170+
yshift=34,
171+
xanchor="left",
172+
font={"size": 18, "color": "#2D2D2D", "family": "Arial, sans-serif"},
173+
)
174+
175+
peak_idx = int(np.argmax(energy))
176+
fig.add_annotation(
177+
x=reaction_coord[peak_idx],
178+
y=energy[peak_idx],
179+
text="<b>Transition State</b><br>120 kJ/mol",
180+
showarrow=True,
181+
ay=-55,
182+
ax=40,
183+
arrowhead=2,
184+
arrowsize=1,
185+
arrowwidth=2,
186+
arrowcolor="#555555",
187+
font={"size": 18, "color": "#2D2D2D", "family": "Arial, sans-serif"},
188+
)
189+
190+
fig.add_annotation(
191+
x=0.98,
192+
y=product_energy,
193+
text="<b>Products</b><br>20 kJ/mol",
194+
showarrow=False,
195+
yshift=-32,
196+
xanchor="right",
197+
font={"size": 18, "color": "#2D2D2D", "family": "Arial, sans-serif"},
198+
)
199+
200+
# Style
201+
fig.update_layout(
202+
title={
203+
"text": "line-reaction-coordinate · plotly · pyplots.ai",
204+
"font": {"size": 28, "family": "Arial, sans-serif", "color": "#2D2D2D"},
205+
"x": 0.5,
206+
"xanchor": "center",
207+
},
208+
xaxis={
209+
"title": {"text": "Reaction Coordinate", "font": {"size": 22, "family": "Arial, sans-serif"}, "standoff": 15},
210+
"tickfont": {"size": 18},
211+
"showgrid": False,
212+
"showticklabels": False,
213+
"zeroline": False,
214+
"range": [-0.08, 1.08],
215+
"showline": True,
216+
"linecolor": "#CCCCCC",
217+
"linewidth": 1,
218+
},
219+
yaxis={
220+
"title": {
221+
"text": "Potential Energy (kJ/mol)",
222+
"font": {"size": 22, "family": "Arial, sans-serif"},
223+
"standoff": 10,
224+
},
225+
"tickfont": {"size": 18},
226+
"gridcolor": "rgba(0,0,0,0.06)",
227+
"gridwidth": 1,
228+
"zeroline": False,
229+
"range": [0, 140],
230+
"showline": True,
231+
"linecolor": "#CCCCCC",
232+
"linewidth": 1,
233+
"dtick": 20,
234+
},
235+
template="plotly_white",
236+
plot_bgcolor="white",
237+
paper_bgcolor="white",
238+
margin={"l": 85, "r": 60, "t": 80, "b": 65},
239+
font={"family": "Arial, sans-serif"},
240+
)
241+
242+
# Save
243+
fig.write_image("plot.png", width=1600, height=900, scale=3)
244+
fig.write_html("plot.html", include_plotlyjs="cdn")

0 commit comments

Comments
 (0)