Source code for plotprofile.cli

"""Command line entry point: plot a reaction profile from JSON files."""

import argparse
import json
import sys
from pathlib import Path

from .plot import ReactionProfilePlotter

EPILOG = """\
Energies are {"Pathway A": [0.0, 12.5, 2.9]}, a bare list, or a list of lists;
null leaves a gap.

Styling lives in --config, a JSON object whose keys are exactly the
ReactionProfilePlotter keyword arguments:

    {"curviness": 0.0,
     "point_type": "dot",
     "labels": false,
     "legend": {"outside": true, "anchor": 1.25},
     "y2": {"label": "bond length", "colors": "plasma"}}

"""


def _load_json(path, what):
    """Read a JSON file, reporting the problem rather than a traceback."""
    try:
        with Path(path).open() as f:
            return json.load(f)
    except FileNotFoundError:
        raise SystemExit(f"plotprofile: {what} file not found: {path}") from None
    except json.JSONDecodeError as e:
        raise SystemExit(f"plotprofile: {what} file is not valid JSON: {path}\n  {e}") from None


[docs] def build_parser(): """Build the argument parser.""" p = argparse.ArgumentParser( prog="plotprofile", description="Plot a reaction profile, scan or IRC from JSON energies.", epilog=EPILOG, formatter_class=argparse.RawDescriptionHelpFormatter, ) p.add_argument("input", help="JSON file of energies") p.add_argument("-o", "--output", default="reaction_profile", help="Output filename, without extension") p.add_argument("-f", "--format", default="png", choices=["png", "svg", "pdf", "eps"], help="Output format") p.add_argument("--dpi", type=int, default=600, help="Raster resolution; ignored for svg/pdf/eps") p.add_argument("--config", help="JSON file of style options (see below)") p.add_argument("--secondary", help="JSON file of series for the right-hand axis") p.add_argument("--x", help="JSON file of real x coordinates, for a scan or IRC") p.add_argument("--annotations", help='JSON file of segment annotations, {"Step 1": [0, 3]}') p.add_argument("--point-labels", help="JSON file of per-point labels") return p
[docs] def main(argv=None): """Plot a profile from JSON files. Returns a process exit code.""" args = build_parser().parse_args(argv) energies = _load_json(args.input, "input") config = _load_json(args.config, "config") if args.config else {} secondary = _load_json(args.secondary, "secondary") if args.secondary else None point_labels = _load_json(args.point_labels, "point-labels") if args.point_labels else None x = _load_json(args.x, "x") if args.x else None if not isinstance(config, dict): raise SystemExit("plotprofile: config must be a JSON object of style options.") annotations = None if args.annotations: raw = _load_json(args.annotations, "annotations") if not isinstance(raw, dict): raise SystemExit('plotprofile: annotations must be a JSON object, e.g. {"Step 1": [0, 3]}') annotations = {label: tuple(span) for label, span in raw.items()} try: ReactionProfilePlotter(**config).plot( energies, secondary=secondary, x=x, annotations=annotations, point_labels=point_labels, filename=args.output, file_format=args.format, dpi=args.dpi, ) except (TypeError, ValueError) as e: raise SystemExit(f"plotprofile: {e}") from None print(f"plotprofile: wrote {args.output}.{args.format}") return 0
if __name__ == "__main__": sys.exit(main())