Module figprint

Expand source code
#!/usr/bin/env python3
# -*- coding: utf-8 -*-


__all__ = ['PrintableFigure', 'custom_plt_figure', 'custom_plt_subplots', 'is_valid_figure', 'print_figure', 'print_pdf', 'print_png', 'print_svg']
"""
📁 figprint.py — Utilities for saving and displaying Matplotlib figures with printing options.

This module extends Matplotlib's `Figure` class by adding convenient methods to export
figures in PDF, PNG, and SVG formats. It also overrides `plt.figure` and `plt.subplots`
to return enhanced `PrintableFigure` objects by default.

Typical usage:
--------------
    import matplotlib.pyplot as plt
    from figprint import print_figure

    # Automatically uses PrintableFigure
    fig, ax = plt.subplots()
    ax.plot([0, 1], [0, 1])
    fig.print()  # Saves to PNG and PDF

    # Advanced control
    fig.print_png("myplot", overwrite=True)
    fig.print_svg("myplot", overwrite=True)

Example:
--------
# myplotmodule.py
from matplotlib import pyplot as plt
import figprint  # Automatically patches pyplot

def example_plot():
    fig, ax = plt.subplots()
    ax.plot([1, 2, 3], [4, 5, 6])
    ax.set_title("Example Plot")
    fig.print("example", overwrite=True)


Author: Generative Simulation Initiative/Olivier Vitrac, PhD
Created: 2025-05-12
"""

import os
from datetime import datetime
import matplotlib.pyplot as plt
from matplotlib.figure import Figure

_fig_metadata_atrr_ = "filename"


def is_valid_figure(fig):
    """
    Check if `fig` is a valid and open Matplotlib figure.
    Parameters:
        fig (object): Any object to check.

    Returns:
        bool: True if the object is an open Matplotlib Figure.
    """
    return isinstance(fig, Figure) and hasattr(fig, 'canvas') and fig.canvas is not None


def _generate_figname(fig, extension):
    """
    Generate a cleaned filename using figure metadata or the current datetime.

    Parameters:
        fig (Figure): Matplotlib figure object.
        extension (str): File extension (e.g. '.pdf').

    Returns:
        str: Cleaned filename with correct extension.
    """
    if hasattr(fig, _fig_metadata_atrr_):
        filename = getattr(fig, _fig_metadata_atrr_)
    else:
        filename = "fig" + datetime.now().strftime("%Y%m%d_%H%M%S")
    filename = filename.strip().replace(" ", "_")
    if not filename.lower().endswith(extension):
        filename += extension
    return filename


def _print_generic(fig, extension, filename, destinationfolder, overwrite, dpi):
    """
    Generic saving logic for figure files.

    Parameters:
        fig (Figure): The figure to save.
        extension (str): File extension (e.g. '.pdf').
        filename (str): Optional filename.
        destinationfolder (str): Folder path.
        overwrite (bool): Overwrite existing file?
        dpi (int): Resolution (ignored for SVG).
    """
    if not is_valid_figure(fig):
        print("no valid figure")
        return

    filename = filename or _generate_figname(fig, extension)
    if not filename.endswith(extension):
        filename += extension
    filepath = os.path.join(destinationfolder, filename)

    if not overwrite and os.path.exists(filepath):
        print(f"File {filepath} already exists. Use overwrite=True to replace it.")
        return

    fig.savefig(filepath, format=extension.lstrip("."), dpi=None if extension == ".svg" else dpi, bbox_inches="tight")
    print(f"Saved {extension.upper()[1:]}: {filepath}")


def print_pdf(fig, filename="", destinationfolder=os.getcwd(), overwrite=False, dpi=300):
    """
    Save a figure as a PDF.
    Example:
    --------
    >>> fig, ax = plt.subplots()
    >>> ax.plot([0, 1], [0, 1])
    >>> print_pdf(fig, "myplot", overwrite=True)
    """
    _print_generic(fig, ".pdf", filename, destinationfolder, overwrite, dpi)


def print_png(fig, filename="", destinationfolder=os.getcwd(), overwrite=False, dpi=300):
    """
    Save a figure as a PNG.

    Example:
    --------
    >>> print_png(fig, "myplot", overwrite=True)
    """
    _print_generic(fig, ".png", filename, destinationfolder, overwrite, dpi)


def print_svg(fig, filename="", destinationfolder=os.getcwd(), overwrite=False):
    """
    Save a figure as an SVG (vector format, dpi-independent).

    Example:
    --------
    >>> print_svg(fig, "myplot", overwrite=True)
    """
    _print_generic(fig, ".svg", filename, destinationfolder, overwrite, dpi=None)


def print_figure(fig, filename="", destinationfolder=os.getcwd(), overwrite=False,
 dpi={"png": 150, "pdf": 300, "svg": None}, what = ["pdf","png","svg"]):
    """
    Save a figure as PDF, PNG, and SVG.

    Example:
    --------
    >>> print_figure(fig, "full_output", overwrite=True)

    Parameters:
        fig (Figure): Figure to save.
        filename (str): Optional base filename.
        destinationfolder (str): Folder path.
        overwrite (bool): Whether to overwrite existing files.
        dpi (dict): Dictionary of resolution per format.
        what (list): list what to print, default = ["pdf","png","svg"]
    """
    if not isinstance(what,(list,tuple)):
        what = [what]
    if is_valid_figure(fig):
        if "pdf" in what: print_pdf(fig, filename, destinationfolder, overwrite, dpi["pdf"])
        if "png" in what: print_png(fig, filename, destinationfolder, overwrite, dpi["png"])
        if "svg" in what: print_svg(fig, filename, destinationfolder, overwrite)
    else:
        print("no valid figure")


class PrintableFigure(Figure):
    """
    Enhanced Matplotlib Figure with custom show and export methods.

    Example:
    --------
    >>> fig, ax = plt.subplots()
    >>> ax.plot([0, 1], [1, 0])
    >>> fig.print("diag1")  # Saves PDF, PNG, SVG
    """

    def show(self, display_mode=None):
        """
        Display figure intelligently based on context (Jupyter/script).

        Parameters:
            display_mode (str): 'auto' or 'classic' (default is 'auto').
        """
        try:
            get_ipython
            if display_mode is None or display_mode == "auto":
                display(self)
            else:
                super().show()
        except NameError:
            super().show()

    def print(self, filename="", destinationfolder=os.getcwd(), overwrite=True,
            dpi={"png": 150, "pdf": 300, "svg": None}):
        """
        Save figure in PDF, PNG, and SVG formats.

        Example:
        --------
        >>> fig.print("summary_figure")
        """
        print_figure(self, filename, destinationfolder, overwrite, dpi)

    def print_pdf(self, filename="", destinationfolder=os.getcwd(), overwrite=False, dpi=300):
        print_pdf(self, filename, destinationfolder, overwrite, dpi)

    def print_png(self, filename="", destinationfolder=os.getcwd(), overwrite=False, dpi=300):
        print_png(self, filename, destinationfolder, overwrite, dpi)

    def print_svg(self, filename="", destinationfolder=os.getcwd(), overwrite=False):
        print_svg(self, filename, destinationfolder, overwrite)


# Save original constructor references
original_plt_figure = plt.figure
original_plt_subplots = plt.subplots

def custom_plt_figure(*args, **kwargs):
    """
    Override `plt.figure()` to return PrintableFigure by default.

    Returns:
        PrintableFigure
    """
    kwargs.setdefault("FigureClass", PrintableFigure)
    return original_plt_figure(*args, **kwargs)

def custom_plt_subplots(*args, **kwargs):
    """
    Override `plt.subplots()` to return PrintableFigure.

    Returns:
        (PrintableFigure, Axes)
    """
    fig, ax = original_plt_subplots(*args, **kwargs)
    fig.__class__ = PrintableFigure
    return fig, ax

# Apply overrides globally
plt.figure = custom_plt_figure
plt.subplots = custom_plt_subplots
plt.FigureClass = PrintableFigure
plt.rcParams['figure.figsize'] = (8, 6)

Functions

def custom_plt_figure(*args, **kwargs)

Override plt.figure() to return PrintableFigure by default.

Returns

PrintableFigure

Expand source code
def custom_plt_figure(*args, **kwargs):
    """
    Override `plt.figure()` to return PrintableFigure by default.

    Returns:
        PrintableFigure
    """
    kwargs.setdefault("FigureClass", PrintableFigure)
    return original_plt_figure(*args, **kwargs)
def custom_plt_subplots(*args, **kwargs)

Override plt.subplots() to return PrintableFigure.

Returns

(PrintableFigure, Axes)

Expand source code
def custom_plt_subplots(*args, **kwargs):
    """
    Override `plt.subplots()` to return PrintableFigure.

    Returns:
        (PrintableFigure, Axes)
    """
    fig, ax = original_plt_subplots(*args, **kwargs)
    fig.__class__ = PrintableFigure
    return fig, ax
def is_valid_figure(fig)

Check if fig is a valid and open Matplotlib figure.

Parameters

fig (object): Any object to check.

Returns

bool
True if the object is an open Matplotlib Figure.
Expand source code
def is_valid_figure(fig):
    """
    Check if `fig` is a valid and open Matplotlib figure.
    Parameters:
        fig (object): Any object to check.

    Returns:
        bool: True if the object is an open Matplotlib Figure.
    """
    return isinstance(fig, Figure) and hasattr(fig, 'canvas') and fig.canvas is not None
def print_figure(fig, filename='', destinationfolder='/home/olivi/natacha/python/utils', overwrite=False, dpi={'png': 150, 'pdf': 300, 'svg': None}, what=['pdf', 'png', 'svg'])

Save a figure as PDF, PNG, and SVG.

Example:

>>> print_figure(fig, "full_output", overwrite=True)

Parameters

fig (Figure): Figure to save. filename (str): Optional base filename. destinationfolder (str): Folder path. overwrite (bool): Whether to overwrite existing files. dpi (dict): Dictionary of resolution per format. what (list): list what to print, default = ["pdf","png","svg"]

Expand source code
def print_figure(fig, filename="", destinationfolder=os.getcwd(), overwrite=False,
 dpi={"png": 150, "pdf": 300, "svg": None}, what = ["pdf","png","svg"]):
    """
    Save a figure as PDF, PNG, and SVG.

    Example:
    --------
    >>> print_figure(fig, "full_output", overwrite=True)

    Parameters:
        fig (Figure): Figure to save.
        filename (str): Optional base filename.
        destinationfolder (str): Folder path.
        overwrite (bool): Whether to overwrite existing files.
        dpi (dict): Dictionary of resolution per format.
        what (list): list what to print, default = ["pdf","png","svg"]
    """
    if not isinstance(what,(list,tuple)):
        what = [what]
    if is_valid_figure(fig):
        if "pdf" in what: print_pdf(fig, filename, destinationfolder, overwrite, dpi["pdf"])
        if "png" in what: print_png(fig, filename, destinationfolder, overwrite, dpi["png"])
        if "svg" in what: print_svg(fig, filename, destinationfolder, overwrite)
    else:
        print("no valid figure")
def print_pdf(fig, filename='', destinationfolder='/home/olivi/natacha/python/utils', overwrite=False, dpi=300)

Save a figure as a PDF. Example:


>>> fig, ax = plt.subplots()
>>> ax.plot([0, 1], [0, 1])
>>> print_pdf(fig, "myplot", overwrite=True)
Expand source code
def print_pdf(fig, filename="", destinationfolder=os.getcwd(), overwrite=False, dpi=300):
    """
    Save a figure as a PDF.
    Example:
    --------
    >>> fig, ax = plt.subplots()
    >>> ax.plot([0, 1], [0, 1])
    >>> print_pdf(fig, "myplot", overwrite=True)
    """
    _print_generic(fig, ".pdf", filename, destinationfolder, overwrite, dpi)
def print_png(fig, filename='', destinationfolder='/home/olivi/natacha/python/utils', overwrite=False, dpi=300)

Save a figure as a PNG.

Example:

>>> print_png(fig, "myplot", overwrite=True)
Expand source code
def print_png(fig, filename="", destinationfolder=os.getcwd(), overwrite=False, dpi=300):
    """
    Save a figure as a PNG.

    Example:
    --------
    >>> print_png(fig, "myplot", overwrite=True)
    """
    _print_generic(fig, ".png", filename, destinationfolder, overwrite, dpi)
def print_svg(fig, filename='', destinationfolder='/home/olivi/natacha/python/utils', overwrite=False)

Save a figure as an SVG (vector format, dpi-independent).

Example:

>>> print_svg(fig, "myplot", overwrite=True)
Expand source code
def print_svg(fig, filename="", destinationfolder=os.getcwd(), overwrite=False):
    """
    Save a figure as an SVG (vector format, dpi-independent).

    Example:
    --------
    >>> print_svg(fig, "myplot", overwrite=True)
    """
    _print_generic(fig, ".svg", filename, destinationfolder, overwrite, dpi=None)

Classes

class PrintableFigure (figsize=None, dpi=None, *, facecolor=None, edgecolor=None, linewidth=0.0, frameon=None, subplotpars=None, tight_layout=None, constrained_layout=None, layout=None, **kwargs)

Enhanced Matplotlib Figure with custom show and export methods.

Example:

>>> fig, ax = plt.subplots()
>>> ax.plot([0, 1], [1, 0])
>>> fig.print("diag1")  # Saves PDF, PNG, SVG

Parameters

figsize : 2-tuple of floats, default: :rc:figure.figsize``
Figure dimension (width, height) in inches.
dpi : float, default: :rc:figure.dpi``
Dots per inch.
facecolor : default: :rc:figure.facecolor``
The figure patch facecolor.
edgecolor : default: :rc:figure.edgecolor``
The figure patch edge color.
linewidth : float
The linewidth of the frame (i.e. the edge linewidth of the figure patch).
frameon : bool, default: :rc:figure.frameon``
If False, suppress drawing the figure background patch.
subplotpars : ~matplotlib.gridspec.SubplotParams
Subplot parameters. If not given, the default subplot parameters :rc:figure.subplot.* are used.
tight_layout : bool or dict, default: :rc:figure.autolayout``

Whether to use the tight layout mechanism. See .set_tight_layout.

Discouraged

The use of this parameter is discouraged. Please use layout='tight' instead for the common case of tight_layout=True and use .set_tight_layout otherwise.

constrained_layout : bool, default: :rc:figure.constrained_layout.use``

This is equal to layout='constrained'.

Discouraged

The use of this parameter is discouraged. Please use layout='constrained' instead.

layout : {'constrained', 'compressed', 'tight', 'none',.LayoutEngine, None}, default: None

The layout mechanism for positioning of plot elements to avoid overlapping Axes decorations (labels, ticks, etc). Note that layout managers can have significant performance penalties.

  • 'constrained': The constrained layout solver adjusts Axes sizes to avoid overlapping Axes decorations. Can handle complex plot layouts and colorbars, and is thus recommended.

See :ref:constrainedlayout_guide for examples.

  • 'compressed': uses the same algorithm as 'constrained', but removes extra space between fixed-aspect-ratio Axes. Best for simple grids of Axes.

  • 'tight': Use the tight layout mechanism. This is a relatively simple algorithm that adjusts the subplot parameters so that decorations do not overlap.

See :ref:tight_layout_guide for examples.

  • 'none': Do not use a layout engine.

  • A .LayoutEngine instance. Builtin layout classes are .ConstrainedLayoutEngine and .TightLayoutEngine, more easily accessible by 'constrained' and 'tight'. Passing an instance allows third parties to provide their own layout engine.

If not given, fall back to using the parameters tight_layout and constrained_layout, including their config defaults :rc:figure.autolayout and :rc:figure.constrained_layout.use.

Other Parameters

**kwargs : .Figure</code> properties, optional
Properties: agg_filter: a filter function, which takes a (m, n, 3) float array and a dpi value, and returns a (m, n, 3) array and two offsets from the bottom left corner of the image alpha: scalar or None animated: bool canvas: FigureCanvas clip_box: ~matplotlib.transforms.BboxBase or None clip_on: bool clip_path: Patch or (Path, Transform) or None constrained_layout: unknown constrained_layout_pads: unknown dpi: float edgecolor: :mpltype:color facecolor: :mpltype:color figheight: float figure: unknown figwidth: float frameon: bool gid: str in_layout: bool label: object layout_engine: {'constrained', 'compressed', 'tight', 'none', .LayoutEngine, None} linewidth: number mouseover: bool path_effects: list of .AbstractPathEffect picker: None or bool or float or callable rasterized: bool size_inches: (float, float) or float sketch_params: (scale: float, length: float, randomness: float) snap: bool or None tight_layout: unknown transform: ~matplotlib.transforms.Transform url: str visible: bool zorder: float
Expand source code
class PrintableFigure(Figure):
    """
    Enhanced Matplotlib Figure with custom show and export methods.

    Example:
    --------
    >>> fig, ax = plt.subplots()
    >>> ax.plot([0, 1], [1, 0])
    >>> fig.print("diag1")  # Saves PDF, PNG, SVG
    """

    def show(self, display_mode=None):
        """
        Display figure intelligently based on context (Jupyter/script).

        Parameters:
            display_mode (str): 'auto' or 'classic' (default is 'auto').
        """
        try:
            get_ipython
            if display_mode is None or display_mode == "auto":
                display(self)
            else:
                super().show()
        except NameError:
            super().show()

    def print(self, filename="", destinationfolder=os.getcwd(), overwrite=True,
            dpi={"png": 150, "pdf": 300, "svg": None}):
        """
        Save figure in PDF, PNG, and SVG formats.

        Example:
        --------
        >>> fig.print("summary_figure")
        """
        print_figure(self, filename, destinationfolder, overwrite, dpi)

    def print_pdf(self, filename="", destinationfolder=os.getcwd(), overwrite=False, dpi=300):
        print_pdf(self, filename, destinationfolder, overwrite, dpi)

    def print_png(self, filename="", destinationfolder=os.getcwd(), overwrite=False, dpi=300):
        print_png(self, filename, destinationfolder, overwrite, dpi)

    def print_svg(self, filename="", destinationfolder=os.getcwd(), overwrite=False):
        print_svg(self, filename, destinationfolder, overwrite)

Ancestors

  • matplotlib.figure.Figure
  • matplotlib.figure.FigureBase
  • matplotlib.artist.Artist

Methods

def print(self, filename='', destinationfolder='/home/olivi/natacha/python/utils', overwrite=True, dpi={'png': 150, 'pdf': 300, 'svg': None})

Save figure in PDF, PNG, and SVG formats.

Example:

>>> fig.print("summary_figure")
Expand source code
def print(self, filename="", destinationfolder=os.getcwd(), overwrite=True,
        dpi={"png": 150, "pdf": 300, "svg": None}):
    """
    Save figure in PDF, PNG, and SVG formats.

    Example:
    --------
    >>> fig.print("summary_figure")
    """
    print_figure(self, filename, destinationfolder, overwrite, dpi)
def print_pdf(self, filename='', destinationfolder='/home/olivi/natacha/python/utils', overwrite=False, dpi=300)
Expand source code
def print_pdf(self, filename="", destinationfolder=os.getcwd(), overwrite=False, dpi=300):
    print_pdf(self, filename, destinationfolder, overwrite, dpi)
def print_png(self, filename='', destinationfolder='/home/olivi/natacha/python/utils', overwrite=False, dpi=300)
Expand source code
def print_png(self, filename="", destinationfolder=os.getcwd(), overwrite=False, dpi=300):
    print_png(self, filename, destinationfolder, overwrite, dpi)
def print_svg(self, filename='', destinationfolder='/home/olivi/natacha/python/utils', overwrite=False)
Expand source code
def print_svg(self, filename="", destinationfolder=os.getcwd(), overwrite=False):
    print_svg(self, filename, destinationfolder, overwrite)
def set(self, *, agg_filter=<UNSET>, alpha=<UNSET>, animated=<UNSET>, canvas=<UNSET>, clip_box=<UNSET>, clip_on=<UNSET>, clip_path=<UNSET>, constrained_layout=<UNSET>, constrained_layout_pads=<UNSET>, dpi=<UNSET>, edgecolor=<UNSET>, facecolor=<UNSET>, figheight=<UNSET>, figwidth=<UNSET>, frameon=<UNSET>, gid=<UNSET>, in_layout=<UNSET>, label=<UNSET>, layout_engine=<UNSET>, linewidth=<UNSET>, mouseover=<UNSET>, path_effects=<UNSET>, picker=<UNSET>, rasterized=<UNSET>, size_inches=<UNSET>, sketch_params=<UNSET>, snap=<UNSET>, tight_layout=<UNSET>, transform=<UNSET>, url=<UNSET>, visible=<UNSET>, zorder=<UNSET>)

Set multiple properties at once.

Supported properties are

Properties

agg_filter: a filter function, which takes a (m, n, 3) float array and a dpi value, and returns a (m, n, 3) array and two offsets from the bottom left corner of the image alpha: scalar or None animated: bool canvas: FigureCanvas clip_box: ~matplotlib.transforms.BboxBase or None clip_on: bool clip_path: Patch or (Path, Transform) or None constrained_layout: unknown constrained_layout_pads: unknown dpi: float edgecolor: :mpltype:color facecolor: :mpltype:color figheight: float figure: unknown figwidth: float frameon: bool gid: str in_layout: bool label: object layout_engine: {'constrained', 'compressed', 'tight', 'none', .LayoutEngine, None} linewidth: number mouseover: bool path_effects: list of .AbstractPathEffect picker: None or bool or float or callable rasterized: bool size_inches: (float, float) or float sketch_params: (scale: float, length: float, randomness: float) snap: bool or None tight_layout: unknown transform: ~matplotlib.transforms.Transform url: str visible: bool zorder: float

Expand source code
cls.set = lambda self, **kwargs: Artist.set(self, **kwargs)
def show(self, display_mode=None)

Display figure intelligently based on context (Jupyter/script).

Parameters

display_mode (str): 'auto' or 'classic' (default is 'auto').

Expand source code
def show(self, display_mode=None):
    """
    Display figure intelligently based on context (Jupyter/script).

    Parameters:
        display_mode (str): 'auto' or 'classic' (default is 'auto').
    """
    try:
        get_ipython
        if display_mode is None or display_mode == "auto":
            display(self)
        else:
            super().show()
    except NameError:
        super().show()