|
| 1 | +""" pyplots.ai |
| 2 | +line-reaction-coordinate: Reaction Coordinate Energy Diagram |
| 3 | +Library: pygal 3.1.0 | Python 3.14.3 |
| 4 | +Quality: 85/100 | Created: 2026-03-21 |
| 5 | +""" |
| 6 | + |
| 7 | +import numpy as np |
| 8 | +import pygal |
| 9 | +from pygal.style import Style |
| 10 | + |
| 11 | + |
| 12 | +# Data - Single-step exothermic reaction |
| 13 | +reactant_energy = 50.0 # kJ/mol |
| 14 | +transition_energy = 120.0 # kJ/mol |
| 15 | +product_energy = 20.0 # kJ/mol |
| 16 | +activation_energy = transition_energy - reactant_energy # Ea = 70 kJ/mol |
| 17 | +enthalpy_change = product_energy - reactant_energy # ΔH = -30 kJ/mol |
| 18 | + |
| 19 | +# Generate smooth energy profile |
| 20 | +n_points = 300 |
| 21 | +reaction_coord = np.linspace(0, 10, n_points) |
| 22 | + |
| 23 | +# Gaussian barrier centered at transition state |
| 24 | +sigma = 1.2 |
| 25 | +peak_pos = 5.0 |
| 26 | +base_curve = reactant_energy + (transition_energy - reactant_energy) * np.exp( |
| 27 | + -0.5 * ((reaction_coord - peak_pos) / sigma) ** 2 |
| 28 | +) |
| 29 | + |
| 30 | +# Vectorized Hermite smoothstep transition to product energy |
| 31 | +t_raw = np.clip((reaction_coord - 6.5) / 2.0, 0.0, 1.0) |
| 32 | +smooth_t = t_raw * t_raw * (3 - 2 * t_raw) |
| 33 | +base_curve = base_curve * (1 - smooth_t) + product_energy * smooth_t |
| 34 | + |
| 35 | +# Vectorized tail flattening |
| 36 | +base_curve = np.where(reaction_coord < 1.5, reactant_energy, base_curve) |
| 37 | +base_curve = np.where(reaction_coord > 8.5, product_energy, base_curve) |
| 38 | + |
| 39 | +# Smooth join regions with repeated convolution |
| 40 | +kernel = np.ones(17) / 17 |
| 41 | +energy_curve = base_curve.copy() |
| 42 | +for _ in range(3): |
| 43 | + padded = np.pad(energy_curve, 17, mode="edge") |
| 44 | + energy_curve = np.convolve(padded, kernel, mode="same")[17:-17] |
| 45 | + |
| 46 | +curve_points = list(zip(reaction_coord.tolist(), energy_curve.tolist(), strict=True)) |
| 47 | + |
| 48 | +# Colorblind-safe palette |
| 49 | +BLUE = "#306998" |
| 50 | +DARK_BLUE = "#1A4971" |
| 51 | +TEAL = "#2980B9" |
| 52 | +ORANGE = "#E67E22" |
| 53 | +GRAY = "#AAAAAA" |
| 54 | + |
| 55 | +# Style — y-guides at key energies serve as reference lines |
| 56 | +custom_style = Style( |
| 57 | + background="white", |
| 58 | + plot_background="#FAFBFC", |
| 59 | + foreground="#2C3E50", |
| 60 | + foreground_strong="#2C3E50", |
| 61 | + foreground_subtle="#CCCCCC", |
| 62 | + colors=(BLUE, GRAY, GRAY, TEAL, ORANGE, DARK_BLUE, BLUE, BLUE), |
| 63 | + title_font_size=60, |
| 64 | + label_font_size=40, |
| 65 | + major_label_font_size=36, |
| 66 | + legend_font_size=32, |
| 67 | + value_font_size=28, |
| 68 | + stroke_width=5, |
| 69 | +) |
| 70 | + |
| 71 | +# Chart |
| 72 | +chart = pygal.XY( |
| 73 | + width=4800, |
| 74 | + height=2700, |
| 75 | + style=custom_style, |
| 76 | + title="line-reaction-coordinate \u00b7 pygal \u00b7 pyplots.ai", |
| 77 | + x_title="Reaction Coordinate", |
| 78 | + y_title="Potential Energy (kJ/mol)", |
| 79 | + show_legend=True, |
| 80 | + legend_at_bottom=True, |
| 81 | + legend_at_bottom_columns=5, |
| 82 | + show_x_guides=False, |
| 83 | + show_y_guides=True, |
| 84 | + show_x_labels=False, |
| 85 | + dots_size=0, |
| 86 | + stroke=True, |
| 87 | + margin=80, |
| 88 | + margin_left=260, |
| 89 | + margin_right=160, |
| 90 | + margin_bottom=200, |
| 91 | + margin_top=120, |
| 92 | + range=(8, 140), |
| 93 | + xrange=(-0.2, 10.2), |
| 94 | + truncate_legend=-1, |
| 95 | + tooltip_border_radius=8, |
| 96 | +) |
| 97 | + |
| 98 | +# Main energy curve |
| 99 | +chart.add("Energy Profile", curve_points, stroke_style={"width": 6}, show_dots=False, fill=False) |
| 100 | + |
| 101 | +# Horizontal reference lines at reactant and product energy levels |
| 102 | +chart.add( |
| 103 | + None, |
| 104 | + [(0.0, reactant_energy), (10.0, reactant_energy)], |
| 105 | + stroke_style={"width": 3, "dasharray": "16, 8"}, |
| 106 | + show_dots=False, |
| 107 | +) |
| 108 | +chart.add( |
| 109 | + None, |
| 110 | + [(0.0, product_energy), (10.0, product_energy)], |
| 111 | + stroke_style={"width": 3, "dasharray": "16, 8"}, |
| 112 | + show_dots=False, |
| 113 | +) |
| 114 | + |
| 115 | +# Ea vertical indicator at transition state peak (teal — colorblind-safe) |
| 116 | +ea_x = peak_pos |
| 117 | +chart.add( |
| 118 | + f"Ea = {activation_energy:.0f} kJ/mol", |
| 119 | + [{"value": (ea_x, reactant_energy), "node": {"r": 14}}, {"value": (ea_x, transition_energy), "node": {"r": 14}}], |
| 120 | + stroke_style={"width": 5, "dasharray": "8, 5"}, |
| 121 | +) |
| 122 | + |
| 123 | +# ΔH vertical indicator (orange — colorblind-safe) |
| 124 | +dh_x = 8.5 |
| 125 | +chart.add( |
| 126 | + f"\u0394H = {enthalpy_change:.0f} kJ/mol", |
| 127 | + [{"value": (dh_x, reactant_energy), "node": {"r": 14}}, {"value": (dh_x, product_energy), "node": {"r": 14}}], |
| 128 | + stroke_style={"width": 5, "dasharray": "8, 5"}, |
| 129 | +) |
| 130 | + |
| 131 | +# Transition state marker (larger, dark blue) |
| 132 | +chart.add( |
| 133 | + "Transition State (\u2021)", |
| 134 | + [{"value": (peak_pos, transition_energy), "node": {"r": 22}}], |
| 135 | + stroke_style={"width": 0}, |
| 136 | + dots_size=22, |
| 137 | +) |
| 138 | + |
| 139 | +# Reactant marker |
| 140 | +chart.add( |
| 141 | + f"Reactants ({reactant_energy:.0f} kJ/mol)", |
| 142 | + [{"value": (1.0, reactant_energy), "node": {"r": 16}}], |
| 143 | + stroke_style={"width": 0}, |
| 144 | + dots_size=16, |
| 145 | +) |
| 146 | + |
| 147 | +# Product marker |
| 148 | +chart.add( |
| 149 | + f"Products ({product_energy:.0f} kJ/mol)", |
| 150 | + [{"value": (9.5, product_energy), "node": {"r": 16}}], |
| 151 | + stroke_style={"width": 0}, |
| 152 | + dots_size=16, |
| 153 | +) |
| 154 | + |
| 155 | +# Custom y-axis labels at chemically meaningful values |
| 156 | +chart.y_labels = [ |
| 157 | + {"label": f"{product_energy:.0f}", "value": product_energy}, |
| 158 | + {"label": f"{reactant_energy:.0f}", "value": reactant_energy}, |
| 159 | + {"label": "80", "value": 80}, |
| 160 | + {"label": "100", "value": 100}, |
| 161 | + {"label": f"{transition_energy:.0f}", "value": transition_energy}, |
| 162 | +] |
| 163 | + |
| 164 | +# Save |
| 165 | +chart.render_to_file("plot.html") |
| 166 | +chart.render_to_png("plot.png") |
0 commit comments