Source code for lifegraph.configuration

from enum import Enum
from pathlib import Path

import matplotlib

_STYLES_DIR = Path(__file__).resolve().parent / "styles"
STYLE_PATH = _STYLES_DIR / "lifegraph.mplstyle"

# A3 is the reference size; all other sizes scale relative to its diagonal.
_A3_DIAG = (11.7**2 + 16.5**2) ** 0.5


def _clamp(val, lo, hi):
    return max(lo, min(hi, val))


def _load_base_style():
    """Read the bundled ``.mplstyle`` file and return it as a dict."""
    rc = matplotlib.rc_params_from_file(str(STYLE_PATH), use_default_template=False)
    return dict(rc)


[docs] class Papersize(Enum): """Supported paper sizes for the life graph figure. Each member maps to standard paper dimensions (in inches) that determine the default figure size and all proportional styling parameters. ISO A-series sizes range from A0 (33.1 x 46.8 in) to A10 (1.0 x 1.5 in). US sizes include Letter (8.5 x 11 in), Legal, JuniorLegal, HalfLetter, Ledger, and Tabloid. The default for :class:`~lifegraph.Lifegraph` is ``Papersize.A3``. Examples -------- >>> from lifegraph import Lifegraph >>> from lifegraph.configuration import Papersize >>> from datetime import date >>> g = Lifegraph(date(1990, 1, 1), size=Papersize.Letter) """ A0 = (33.1, 46.8) A1 = (23.4, 33.1) A2 = (16.5, 23.4) A3 = (11.7, 16.5) A4 = (8.3, 11.7) A5 = (5.8, 8.3) A6 = (4.1, 5.8) A7 = (2.9, 4.1) A8 = (2.0, 2.9) A9 = (1.5, 2.0) A10 = (1.0, 1.5) HalfLetter = (5.5, 8.5) Letter = (8.5, 11.0) Legal = (8.5, 14.0) JuniorLegal = (5.0, 8.0) Ledger = (11.0, 17.0) Tabloid = (17.0, 11.0)
[docs] class LifegraphParams: """Drawing parameters scaled to a particular paper size. On construction, two dictionaries are populated: * ``rcParams`` -- values forwarded to :func:`matplotlib.pyplot.rcParams.update`. * ``otherParams`` -- lifegraph-specific styling knobs (annotation offsets, watermark size, era-span line widths, etc.). Parameters ---------- papersize : Papersize The paper size to configure for. Attributes ---------- rcParams : dict Matplotlib RC parameter overrides. otherParams : dict Lifegraph-specific styling parameters. Examples -------- >>> from lifegraph.configuration import LifegraphParams, Papersize >>> params = LifegraphParams(Papersize.A4) >>> params.rcParams["figure.figsize"] [8.3, 11.7] """ def __init__(self, papersize): self.size = papersize w, h = papersize.value s = (w**2 + h**2) ** 0.5 / _A3_DIAG self.rcParams = _load_base_style() self.rcParams.update( { "figure.figsize": [w, h], "axes.labelsize": _clamp(round(16 * s), 1, 40), "figure.titlesize": _clamp(round(28 * s), 4, 128), "font.size": _clamp(round(18 * s), 1, 60), "lines.linewidth": round(max(0.2, 0.5 * s), 2), "lines.markersize": round(max(0.5, 4.5 * s), 2), "lines.markeredgewidth": round(max(0.01, 0.50 * s), 2), "xtick.labelsize": _clamp(round(10 * s), 1, 20), "ytick.labelsize": _clamp(round(10 * s), 1, 20), "savefig.pad_inches": ( 0.50 if s > 0.8 else (0.25 if s > 0.4 else 0.05) ), } ) self.otherParams = { "xlabel.position": (0.20, 1.05), "xlabel.color": None, "xlabel.fontsize": None, "ylabel.position": (-0.03, 0.95), "ylabel.color": None, "ylabel.fontsize": None, "maxage.fontsize": _clamp(round(20 * s), 2, 38), "figure.title.yposition": 0.95 if s > 0.3 else 0.97, "annotation.marker.size": round(max(0.001, 8.0 * s), 2), "annotation.edge.width": round(max(0.1, 0.8 * s), 2), "annotation.line.width": round(max(0.1, 1.0 * s), 2), "annotation.shrinkA": 0, "annotation.left.offset": 6 if s < 1.5 else 3, "annotation.right.offset": 5 if s < 1.5 else 3, "era.span.linestyle": "-", "era.span.markersize": 0, "era.line.linewidth": round(max(0.2, 1.0 * s), 2), "watermark.fontsize": _clamp(round(120 * s), 18, 200), }