Source code for pastas.plotting.plotutil

"""This module contains utility functions for plotting."""

import logging
from typing import List, Union

import matplotlib.pyplot as plt
import numpy as np
from pandas import Series, Timedelta

from pastas.typing import Axes

logger = logging.getLogger(__name__)


def _table_formatter_params(s: float, na_rep: str = "") -> str:
    """Internal method for formatting parameters in tables in Pastas plots.

    Parameters
    ----------
    s : float
        value to format.

    Returns
    -------
    str
        float formatted as str.
    """
    if np.isnan(s):
        return na_rep
    elif np.floor(np.log10(np.abs(s))) <= -2:
        return f"{s:.2e}"
    elif np.floor(np.log10(np.abs(s))) > 5:
        return f"{s:.2e}"
    else:
        return f"{s:.2f}"


def _table_formatter_stderr(s: float, na_rep: str = "") -> str:
    """Internal method for formatting stderrs in tables in Pastas plots.

    Parameters
    ----------
    s : float
        value to format.

    Returns
    -------
    str
        float formatted as str.
    """
    if np.isnan(s):
        return na_rep
    elif s == 0.0:
        return f{s * 100:.2e}%"
    elif np.floor(np.log10(np.abs(s))) <= -4:
        return f{s * 100.0:.2e}%"
    elif np.floor(np.log10(np.abs(s))) > 3:
        return f{s * 100.0:.2e}%"
    else:
        return f{s:.2%}"


def _get_height_ratios(ylims: List[Union[list, tuple]]) -> List[float]:
    return [0.0 if np.isnan(ylim[1] - ylim[0]) else ylim[1] - ylim[0] for ylim in ylims]


def _get_stress_series(ml, split: bool = True) -> List[Series]:
    stresses = []
    for name in ml.stressmodels.keys():
        nstress = len(ml.stressmodels[name].stress)
        if split and nstress > 1:
            for istress in range(nstress):
                stress = ml.get_stress(name, istress=istress)
                stresses.append(stress)
        else:
            stress = ml.get_stress(name)
            if isinstance(stress, list):
                stresses.extend(stress)
            else:
                stresses.append(stress)
    return stresses


[docs]def share_xaxes(axes: List[Axes]) -> None: """share x-axes""" for i, iax in enumerate(axes): if i < (len(axes) - 1): iax.sharex(axes[-1]) for t in iax.get_xticklabels(): t.set_visible(False)
[docs]def share_yaxes(axes: List[Axes]) -> None: """share y-axes""" for iax in axes[1:]: iax.sharey(axes[0]) for t in iax.get_yticklabels(): t.set_visible(False)
[docs]def plot_series_with_gaps( series: Series, gap: Timedelta | None = None, ax: Axes | None = None, **kwargs ) -> Axes: """Plot a pandas Series with gaps if index difference is larger than gap. Parameters ---------- series: pd.Series The series to plot. gap: Timedelta | None Timedelta to be considered as a gap. If the difference between two consecutive index values is larger than gap, a gap is inserted in the plot. If None, the maximum value between the 95th percentile of the differences and 50 days is used as gap. ax: Axes | None The axes to plot on. if None, a new figure is created. kwargs: dict Additional keyword arguments that are passed to the plot method. """ if ax is None: _, ax = plt.subplots() if gap is None: gapq = np.quantile(series.index.diff().dropna(), 0.95) gap = max(gapq, Timedelta(50, unit="D")) s_split = (series.index.diff() >= gap).cumsum() series.name = kwargs.pop("label") if "label" in kwargs else series.name color = kwargs.pop("c", "k") color = kwargs.pop("color", color) for i, gr in series.groupby(s_split): label = None if i > 0 else series.name if len(gr) == 1: logger.info( "Isolated point found in series %s with gap larger than %s days", series.name, gap / Timedelta(1, "D"), ) ax.scatter(gr.index, gr.values, label=label, marker="_", s=3.0, color=color) ax.plot(gr.index, gr.values, label=label, color=color, **kwargs) return ax