Python code for quick plotting of professional looking reaction profiles with various customisation options available
More information can be found at ReadTheDocs
Can be used with colab.ipynb without a local install.
Simplest installation:
pip install plotprofileor from the latest version:
pip install git+https://github.com/aligfellow/plotprofile.gitgit clone git@github.com:aligfellow/plotprofile.git
cd plotprofile
pip install .from plotprofile import ReactionProfilePlotter
energy_sets = {
"Pathway A": [0.00, -2.0, 10.2, 1.4, -1.5, 2.0, -7.2],
"Pathway B": [None, -2.0, 6.2, 4.3, 5.8, 2.0],
}
plotter = ReactionProfilePlotter()
plotter.plot(energy_sets, filename="images/profile0")from plotprofile import ReactionProfilePlotter
energy_sets = {
"Pathway A": [0.00, -2.0, 10.2, 1.4, -1.5, 2.0, -7.2],
"Pathway B": [None, -2.0, 6.2, 4.3, 5.8, 2.0],
"Pathway C": [None, -2.0, -6.8,-6.8, None, -2.0],
"diastereomer": [None, None, 12.2],
"diastereomer2": [None, None, 9.8, 9.8]
}
annotations = {
'Step 1': (0,3),
'Step 2': (3,5),
'Step 3': (5,6),
}
plotter = ReactionProfilePlotter(linestyle={"Pathway C": "--"})
plotter.plot(energy_sets, annotations=annotations, filename="images/profile1")Passing in annotations for labelling of the reaction profile:
- this is done in the plotting function rather than the class
- using dictionary with keys of labels and a tuple of the start and end x-indices
- allowing for multiple plots of the same style with different annotations
A variety of other paremters can be tuned for the plotting, including:
axes="box|y|x|both|None"curviness=0.42- reduce for less curve and vice versacolors=["list","of","colors"]|cmap- specify colour list or colour map- if the colour list is too short then colours will be repeated.
- if the cmap is invalid,
viridiswill be set as a default
linestyle- a matplotlib linestyle for every series, or a dict of them keyed by series label.'--'and solid use the package's own dash (scaled to the line, spaced bydash_spacing);'-.',':'and(offset, (on, off))tuples go straight to matplotlibshow_legend=Boollegend={...}- passed straight to matplotlib'sax.legend(), soloc,frameon,fontsize,ncols,title,framealphaetc. all workunits="kj|kcal"energy="e|electronic|g|gibbs|h|enthalpy|s|entropy|"x_labelandy_labelcan be used to set cutoms axis labels, superceedingunitsorenergy
Using style="presentation" which sets a larger figsize=(X,X) with thicker lines and a larger font size:
plotter = ReactionProfilePlotter(style="presentation", linestyle={"Pathway B": "--"}, point_type='dot', desaturate=False, colors='Blues_r', show_legend=False, curviness=0.5, x_label='Reaction Profile', y_label='Free Energy (kcal/mol)')
plotter.plot(energy_sets, filename="images/profile2")- Straight lines set in a style, which can also be done by passing in
curviness=0 - Labels can be placed below the annotation arrow
- Some parameters regarding the plotting data can be tuned in
ReactionProfilePlotter.plot:include_keys- only some of the energy_sets keys() included in the plotexclude_from_legend- excluded one of the energy_sets key from the legend
plotter = ReactionProfilePlotter(style="straight", figsize=(6,4), linestyle={"Pathway C": "--"}, point_type='bar', annotation_color='black', axes='y', colors=['midnightblue', 'slateblue', 'darkviolet'], energy='electronic', units='kj', annotation_below_arrow=True, dash_spacing=5.0, desaturate=False)
plotter.plot(energy_sets, annotations=annotations, filename="images/profile3", exclude_from_legend=["Pathway B"], include_keys=["Pathway A", "Pathway B", "Pathway C", "diastereomer"])- Point labels can be also added by passing
point_labelstoReactionProfilePlotter.plot - Annotations can accomodate newline characters
\nand spacing will be adjusted automatically
from plotprofile import ReactionProfilePlotter
energy_sets = {
"1": [-3.0, 12.5, 2.9, 0.0, 1.8, 10.5, 2.9]
}
annotations = {
'Step 1': (0,3),
'Step 2\nAlternate': (3,6),
}
point_labels = {
"1": [None, "TS1", None, "Int1", None, "TS2"]
}
plotter = ReactionProfilePlotter(figsize=(4.5,4), axes='box', show_legend=False)
plotter.plot(energy_sets, annotations=annotations, point_labels=point_labels, filename="images/profile4")- Bar lengths and widths can be adjusted
- Default line/curve behaviour with bars is to connect at the edges, this can be turned off with
connect_bar_ends=False - Dash spacing of the line can be changed with
dash_spacing
from plotprofile import ReactionProfilePlotter
energy_sets = {
"1": [-3.0, 12.5, 2.9, 0.0, 1.8, 10.5, 2.9]
}
annotations = {
'Step 1': (0,3),
'Step 2\nAlternate': (3,6),
}
point_labels = {
"1": [None, "TS1", None, "Int1", None, "TS2"]
}
plotter = ReactionProfilePlotter(figsize=(4.5,4), axes='box', curviness=0.5, show_legend=False, point_type='bar', bar_length=0.3, bar_width=3, connect_bar_ends=False, linestyle={"1": "--"}, dash_spacing=1.5)
plotter.plot(energy_sets, annotations=annotations, point_labels=point_labels, filename="images/profile5")secondary={label: [values]} in plot() adds a right-hand axis, for quantities that share the reaction coordinate but not the units. Same x-indices, same legend.
Style keys set both axes. y1 and y2 take the same keys and override one axis alone; y2 defaults to its own palette and linestyle='--'.
plotter = ReactionProfilePlotter(figsize=(7.6,4), curviness=0.0, labels=False, energy='E', square=True,
x_label='reaction coordinate λ',
legend={'outside': True, 'anchor': 1.22},
y2={'label': 'bond length (Å)', 'colors': 'plasma'})
plotter.plot({"force field": [0.0, 5.2, 12.4, 3.1, -1.2], "g-xTB": [0.0, 4.4, 10.9, 2.0, -2.5]},
secondary={"C1-O3 (breaking)": [1.43, 1.62, 2.10, 2.85, 3.30],
"C1-O14 (forming)": [3.20, 2.75, 2.05, 1.55, 1.42]},
filename="images/profile24")y2 takes colors, curviness, linestyle, line_width, marker_size, point_type, bar_length, bar_width, connect_bar_ends, desaturate, desaturate_factor and dash_spacing:
ReactionProfilePlotter(curviness=0.0, point_type='bar', # both axes
y2={'point_type': 'dot', 'curviness': 0.42, 'linestyle': 'solid'}) # right axis onlylegend is passed to matplotlib's ax.legend(), so frameon, edgecolor, facecolor, framealpha, labelcolor, ncols, title, bbox_to_anchor etc. all work. Plus two extras: outside=True puts the legend beside the axes (anchor sets how far), and frameon defaults to on inside, off outside.
The plot font is bold by default, and the legend inherits it. Use matplotlib's prop to unbold the legend alone:
plotter = ReactionProfilePlotter(labels=False, legend={
'loc': 'lower left',
'frameon': True, 'edgecolor': 'maroon', 'facecolor': 'whitesmoke', # border
'labelcolor': 'darkcyan', # text colour
'prop': {'weight': 'normal'}, # not bold
'fontsize': 9,
})
plotter.plot(energy_sets, filename="images/profile25")By default the x-axis is the point index, evenly spaced, which is what a schematic profile wants. For a scan or an IRC it is a real quantity, so pass x to plot():
r = [1.5, 1.8, 2.1, 2.2, 2.4, 3.5, 4.5] # uneven steps, dense near the TS
E = [0.0, 4.0, 9.0, 11.0, 12.6, 3.0, 1.0]
plotter = ReactionProfilePlotter(
curviness=0.0, # join the computed points directly
labels=False,
point_type='dot',
x_indices=True, # show the x ticks
axes='both',
x_label='r(C-Cl) / Å',
energy='E',
)
plotter.plot({"scan": E}, x=r)Note
xmust cover every index the energies use, and applies to the secondary series too.- Gaps (
None) and repeated values still work: a repeat sits at the midpoint of the twoxvalues it spans. annotationsare still given in indices, notxvalues.bar_lengthis in x-axis units, so scale it to the range ofx.
Plots can be saved by passing filename to plotter.plot(). The output format is controlled by file_format and supports any standard matplotlib format (e.g. png, svg, pdf, eps).
svg, pdf and eps are vector: scalable, with the text left as real text, so labels stay editable in Illustrator or Inkscape.
plotter.plot(energy_sets, filename="my_profile", file_format="svg")dpi (default 600) only applies to raster formats such as png; it has no meaningful effect on vector output.
plotter.plot(energy_sets, filename="my_profile", file_format="png", dpi=300)Important
- Secondary curves can begin from after the 1st point, just need to have a
Noneentry in the list of energies e.g.[None, 0.0, 1.0] - Individual points can be placed if this is a list with only one energy value (e.g. uncluttered diastereomeric TS for example, see examples)
- labels of theses are not added to the legend
- these can even be placed as individual points between two indices with
[None, 5.0, 5.0]
- Spacing of points on the profile can be altered by:
- passing the same energy twice in a row, which will place the point halfway between the two x-indices, i.e. Pathway C point in examples, e.g.
[0.0, 5.0, 5.0] - with an entry like
[0.0, None, 1.0]which will have a line connecting indexes 0 and 2 of this list with the correct x-axis alignment
- passing the same energy twice in a row, which will place the point halfway between the two x-indices, i.e. Pathway C point in examples, e.g.
- data types can be:
- dict, with labels for the legend
- list of lists (no labelling of different profiles)
- single list
Installing gives a plotprofile command. Data comes in as JSON files; styling lives in a --config JSON whose keys are exactly the ReactionProfilePlotter arguments. An unknown key is an error, not a warning.
plotprofile examples/input.json -o profile -f svg
plotprofile examples/input.json --config examples/config.json --annotations examples/annotations.json
plotprofile scan.json --secondary bonds.json --x coord.json --config scan.json -o irc -f svgplotprofile INPUT [-o OUT] [-f {png,svg,pdf,eps}] [--dpi N]
[--config FILE] [--secondary FILE] [--x FILE]
[--annotations FILE] [--point-labels FILE]
See examples/config.json and the CLI docs.
The behavior can be customized via styles.json or by passing parameters to ReactionProfilePlotter().
The full set of options, and the default, presentation and straight presets, are in src/plotprofile/styles.json — also rendered in the style docs.
git clone https://github.com/aligfellow/plotprofile.git
cd plotprofile
just setup # install dev dependencies and pre-commit
just check # lint + type-check + tests| Command | Description |
|---|---|
just check |
Run lint + type-check + tests |
just lint |
Format and lint with ruff |
just type |
Type-check with ty |
just test |
Run pytest with coverage |
just fix |
Auto-fix lint issues |
just build |
Build distribution |
just setup |
Install all dev dependencies |
GitHub Actions runs the same checks on every push to main and every PR.
Generated from aligfellow/python-template; pull in later template changes with copier update --trust.







