"""Classes for the storage of FancyPlots relevant data.
The class :class:`ccu.fancyplots.data.FancyCache` describes how data is
imported to and exported from FancyPlots.
The class :class:`ccu.fancyplots.data.FEDData` defines how free energy
diagram is stored.
The class :class:`ccu.fancyplots.data.FormattingParameters` defines how
plot formatting parameters are stored.
The class :class:`ccu.fancyplots.data.Annotation` defines how plot
annotations are stored.
Examples:
The internal data from FancyPlots can be accessed via the attribute
:attr:`ccu.fancyplots._gui.root.FancyPlotsGUI.cache` and dumped into
a human-readable form via the following idiom:
>>> import json
>>> from pathlib import Path
>>> from ccu.fancyplots.data import DEFAULT_PARAMETERS
>>> from ccu.fancyplots.data import FancyCache
>>> data = {
... "energy_data": [
... [0, 0.2, 0.33, -0.5],
... [0, -0.1, 0.25, 0],
... "mechanism": ["*", "*CHOO", "*CO", "CO"],
... "legend_labels: ["Cu(111)", "Cu-NP"],
>>> annotations = []
>>> formatting_parameters = DEFAULT_PARAMETERS
>>> cache = FancyCache(
... formatting_parameters,
... diagram_data=data,
... annotations=annotations,
...)
>>> with Path("fancy.cache").open(
... mode="w", encoding="utf-8"
... ) as file: # doctest:+SKIP
... _ = json.dump(cache, file, indent=2)
The same file (``fancy.cache``) can be loaded into a convenient format for
FancyPlots via the following idiom:
>>> import json
>>> from pathlib import Path
>>> from ccu.fancyplots.data import FancyCache
>>> with Path("fancy.cache").open( # doctest:+SKIP
... mode="r", encoding="utf-8"
... ) as file:
... cache = FancyCache(**json.load(file))
"""
from dataclasses import asdict
from dataclasses import dataclass
import json
from pathlib import Path
from typing import Annotated
from typing import NamedTuple
from typing import TypedDict
FANCY_EXTENSION = ".fancy"
[docs]
class FEDData(TypedDict):
"""Energies and metadata for a free energy diagram.
Keys:
energy_data: A list of lists. Each list defines energies for each step
in the mechanism.
mechanism: The labels for each mechanism step.
legend_labels: The legend labels for each pathway.
Valid :class:`ccu.fancyplots.data.FEDData` instances should satisfy the following
condition for seamless use with ``FancyPlots``:
- The lengths of each element in
:attr:`ccu.fancyplots.data.FEDData.energy_data` should be equal to the
lengths of :attr:`ccu.fancyplots.data.FEDData.mechanism` and
:attr:`ccu.fancyplots.data.FEDData.legend_labels`. This translates to
the condition that the number of energies defined for each step should
equal the number of steps in the mechanism.
"""
energy_data: list[list[float | None]]
mechanism: list[str]
legend_labels: list[str | None]
# TODO: add missing parameters:
# xlim, ylim
#: The default formatting parameters for `FancyPlots`
DEFAULT_PARAMETERS = FormattingParameters(
boxsize=(6, 4.5),
font="Sans Serif",
fontsize=14,
markeredgewidth=0.3,
markersize=7,
linewidth=2.25,
xlim=None,
ylim=None,
xscale=None,
yscale="linear",
tick_loc="out",
tick_dec=2,
tick_min=0,
tick_double=False,
legend_loc="best",
xlabel="Reaction Coordinate",
ylabel=r"$\Delta$G / eV",
colors=("black", "blue", "red", "lime", "fuchsia"),
savename="fed.svg",
dpi=1200,
title="",
)
[docs]
class Annotation(NamedTuple):
"""A free energy diagram annotation.
Attributes:
color: The color to use for the annotation.
fontsize: The fontsize to use for the annotation.
text: The annotation text.
x: The x-coordinate of the annotation.
y: The y-coordinate of the annotation.
"""
color: str = "k"
fontsize: float = round(DEFAULT_PARAMETERS["fontsize"] * 0.8, 1)
text: str = ""
x: float = 0.0
y: float = 0.0
[docs]
@dataclass
class FancyCache:
"""The data model for caching/loading data into FancyPlots.
Attributes:
style_parameters: The formatting parameters to use to plot the
free energy diagram.
diagram_data: The energy data and metadata for plotting the free
energy diagram.
annotations: The annotations associated with the free energy diagram.
"""
style_parameters: FormattingParameters
diagram_data: FEDData
annotations: list[Annotation]
[docs]
def save(self, savename: str | Path = f"cache.{FANCY_EXTENSION}") -> None:
"""Save the FancyCache.
Args:
savename: The filename in which to save the cache. Defaults to
f"cache.{FANCY_EXTENSION}".
"""
with Path(savename).open(mode="w", encoding="utf-8") as file:
json.dump(asdict(self), file, indent=2)