Skip to content

Base Plot

Base class for all plots, ensuring a consistent interface and style.

BasePlot

Base class for all plots.

Handles figure and axis creation, applies a consistent style, and provides a common interface for saving and closing plots.

Source code in src/monet_plots/plots/base.py
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
class BasePlot:
    """Base class for all plots.

    Handles figure and axis creation, applies a consistent style,
    and provides a common interface for saving and closing plots.
    """

    def __init__(self, fig=None, ax=None, style: str | None = "wiley", **kwargs):
        """Initializes the plot with a consistent style.

        If `fig` and `ax` are not provided, a new figure and axes
        are created.

        Args:
            fig (matplotlib.figure.Figure, optional): Figure to plot on.
            ax (matplotlib.axes.Axes, optional): Axes to plot on.
            style (str, optional): Style name to apply (e.g., 'wiley', 'paper').
                If None, no style is applied. Defaults to 'wiley'.
            **kwargs: Additional keyword arguments for `plt.subplots`.
        """
        if style:
            set_style(style)

        if ax is not None:
            self.ax = ax
            if fig is not None:
                self.fig = fig
            else:
                self.fig = ax.figure
        elif fig is not None:
            self.fig = fig
            self.ax = None
        else:
            self.fig, self.ax = plt.subplots(**kwargs)

    def save(self, filename, **kwargs):
        """Saves the plot to a file.

        Args:
            filename (str): The name of the file to save the plot to.
            **kwargs: Additional keyword arguments for `savefig`.
        """
        self.fig.savefig(filename, **kwargs)

    def close(self):
        """Closes the plot figure."""
        plt.close(self.fig)

    def add_logo(
        self,
        logo: str | Any | None = None,
        *,
        ax: matplotlib.axes.Axes | None = None,
        loc: str = "upper right",
        scale: float = 0.1,
        pad: float = 0.05,
        **kwargs: Any,
    ) -> Any:
        """Adds a logo to the plot.

        Parameters
        ----------
        logo : str or array-like, optional
            Path to the logo image, a URL, or a numpy array.
            If None, the default MONET logo is used.
        ax : matplotlib.axes.Axes, optional
            The axes to add the logo to. Defaults to `self.ax`.
        loc : str, optional
            Location of the logo ('upper right', 'upper left', 'lower right',
            'lower left', 'center'). Defaults to "upper right".
        scale : float, optional
            Scaling factor for the logo, by default 0.1.
        pad : float, optional
            Padding from the edge of the axes, by default 0.05.
        **kwargs : Any
            Additional keyword arguments passed to `AnnotationBbox`.

        Returns
        -------
        matplotlib.offsetbox.AnnotationBbox
            The added logo object.
        """
        import matplotlib.image as mpimg
        from matplotlib.offsetbox import AnnotationBbox, OffsetImage

        from ..plot_utils import get_logo_path

        if ax is None:
            ax = self.ax

        if logo is None:
            logo = get_logo_path()

        if isinstance(logo, str):
            if logo.startswith("http"):
                import io
                import urllib.request

                with urllib.request.urlopen(logo) as url:
                    f = io.BytesIO(url.read())
                img = mpimg.imread(f)
            else:
                img = mpimg.imread(logo)
        else:
            img = logo

        imagebox = OffsetImage(img, zoom=scale)
        imagebox.image.axes = ax

        # Mapping of location strings to axes fraction coordinates and box alignment
        loc_map = {
            "upper right": ((1 - pad, 1 - pad), (1, 1)),
            "upper left": ((pad, 1 - pad), (0, 1)),
            "lower right": ((1 - pad, pad), (1, 0)),
            "lower left": ((pad, pad), (0, 0)),
            "center": ((0.5, 0.5), (0.5, 0.5)),
        }

        if loc in loc_map:
            xy, box_alignment = loc_map[loc]
        else:
            # If loc is not a string in loc_map, assume it might be a coordinate
            # tuple, but for simplicity we default to upper right if it's invalid
            if isinstance(loc, tuple) and len(loc) == 2:
                xy = loc
                box_alignment = (0.5, 0.5)
            else:
                xy, box_alignment = loc_map["upper right"]

        ab = AnnotationBbox(
            imagebox,
            xy,
            xycoords="axes fraction",
            box_alignment=box_alignment,
            pad=0,
            frameon=False,
            **kwargs,
        )

        ax.add_artist(ab)
        return ab

    def add_colorbar(
        self,
        mappable: matplotlib.cm.ScalarMappable,
        *,
        ax: matplotlib.axes.Axes | None = None,
        label: str | None = None,
        loc: str = "right",
        size: str = "5%",
        pad: float = 0.05,
        **kwargs: Any,
    ) -> matplotlib.colorbar.Colorbar:
        """Add a colorbar that matches the axes size.

        This method uses `inset_axes` to ensure the colorbar height (or width)
        matches the axes dimensions exactly, which is particularly useful for
        geospatial plots with fixed aspects.

        Parameters
        ----------
        mappable : matplotlib.cm.ScalarMappable
            The mappable object (e.g., from imshow, scatter, contourf).
        ax : matplotlib.axes.Axes, optional
            The axes to attach the colorbar to. Defaults to `self.ax`.
        label : str, optional
            Label for the colorbar, by default None.
        loc : str, optional
            Location of the colorbar ('right', 'left', 'top', 'bottom'),
            by default "right".
        size : str, optional
            Width (if vertical) or height (if horizontal) of the colorbar,
            as a percentage of the axes, by default "5%".
        pad : float, optional
            Padding between the axes and the colorbar, by default 0.05.
        **kwargs : Any
            Additional keyword arguments passed to `fig.colorbar`.

        Returns
        -------
        matplotlib.colorbar.Colorbar
            The created colorbar object.
        """
        from mpl_toolkits.axes_grid1.inset_locator import inset_axes

        if ax is None:
            ax = self.ax

        orientation = "vertical" if loc in ["right", "left"] else "horizontal"

        # Determine anchor and position based on location
        if loc == "right":
            bbox_to_anchor = (1.0 + pad, 0.0, 1.0, 1.0)
            width, height = size, "100%"
        elif loc == "left":
            bbox_to_anchor = (-(float(size.strip("%")) / 100.0 + pad), 0.0, 1.0, 1.0)
            width, height = size, "100%"
        elif loc == "top":
            bbox_to_anchor = (0.0, 1.0 + pad, 1.0, 1.0)
            width, height = "100%", size
        else:  # bottom
            bbox_to_anchor = (0.0, -(float(size.strip("%")) / 100.0 + pad), 1.0, 1.0)
            width, height = "100%", size

        cax = inset_axes(
            ax,
            width=width,
            height=height,
            loc="lower left",
            bbox_to_anchor=bbox_to_anchor,
            bbox_transform=ax.transAxes,
            borderpad=0,
        )

        cb = self.fig.colorbar(mappable, cax=cax, orientation=orientation, **kwargs)

        if label:
            cb.set_label(label)

        return cb

__init__(fig=None, ax=None, style='wiley', **kwargs)

Initializes the plot with a consistent style.

If fig and ax are not provided, a new figure and axes are created.

Parameters:

Name Type Description Default
fig Figure

Figure to plot on.

None
ax Axes

Axes to plot on.

None
style str

Style name to apply (e.g., 'wiley', 'paper'). If None, no style is applied. Defaults to 'wiley'.

'wiley'
**kwargs

Additional keyword arguments for plt.subplots.

{}
Source code in src/monet_plots/plots/base.py
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
def __init__(self, fig=None, ax=None, style: str | None = "wiley", **kwargs):
    """Initializes the plot with a consistent style.

    If `fig` and `ax` are not provided, a new figure and axes
    are created.

    Args:
        fig (matplotlib.figure.Figure, optional): Figure to plot on.
        ax (matplotlib.axes.Axes, optional): Axes to plot on.
        style (str, optional): Style name to apply (e.g., 'wiley', 'paper').
            If None, no style is applied. Defaults to 'wiley'.
        **kwargs: Additional keyword arguments for `plt.subplots`.
    """
    if style:
        set_style(style)

    if ax is not None:
        self.ax = ax
        if fig is not None:
            self.fig = fig
        else:
            self.fig = ax.figure
    elif fig is not None:
        self.fig = fig
        self.ax = None
    else:
        self.fig, self.ax = plt.subplots(**kwargs)

add_colorbar(mappable, *, ax=None, label=None, loc='right', size='5%', pad=0.05, **kwargs)

Add a colorbar that matches the axes size.

This method uses inset_axes to ensure the colorbar height (or width) matches the axes dimensions exactly, which is particularly useful for geospatial plots with fixed aspects.

Parameters

mappable : matplotlib.cm.ScalarMappable The mappable object (e.g., from imshow, scatter, contourf). ax : matplotlib.axes.Axes, optional The axes to attach the colorbar to. Defaults to self.ax. label : str, optional Label for the colorbar, by default None. loc : str, optional Location of the colorbar ('right', 'left', 'top', 'bottom'), by default "right". size : str, optional Width (if vertical) or height (if horizontal) of the colorbar, as a percentage of the axes, by default "5%". pad : float, optional Padding between the axes and the colorbar, by default 0.05. **kwargs : Any Additional keyword arguments passed to fig.colorbar.

Returns

matplotlib.colorbar.Colorbar The created colorbar object.

Source code in src/monet_plots/plots/base.py
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
def add_colorbar(
    self,
    mappable: matplotlib.cm.ScalarMappable,
    *,
    ax: matplotlib.axes.Axes | None = None,
    label: str | None = None,
    loc: str = "right",
    size: str = "5%",
    pad: float = 0.05,
    **kwargs: Any,
) -> matplotlib.colorbar.Colorbar:
    """Add a colorbar that matches the axes size.

    This method uses `inset_axes` to ensure the colorbar height (or width)
    matches the axes dimensions exactly, which is particularly useful for
    geospatial plots with fixed aspects.

    Parameters
    ----------
    mappable : matplotlib.cm.ScalarMappable
        The mappable object (e.g., from imshow, scatter, contourf).
    ax : matplotlib.axes.Axes, optional
        The axes to attach the colorbar to. Defaults to `self.ax`.
    label : str, optional
        Label for the colorbar, by default None.
    loc : str, optional
        Location of the colorbar ('right', 'left', 'top', 'bottom'),
        by default "right".
    size : str, optional
        Width (if vertical) or height (if horizontal) of the colorbar,
        as a percentage of the axes, by default "5%".
    pad : float, optional
        Padding between the axes and the colorbar, by default 0.05.
    **kwargs : Any
        Additional keyword arguments passed to `fig.colorbar`.

    Returns
    -------
    matplotlib.colorbar.Colorbar
        The created colorbar object.
    """
    from mpl_toolkits.axes_grid1.inset_locator import inset_axes

    if ax is None:
        ax = self.ax

    orientation = "vertical" if loc in ["right", "left"] else "horizontal"

    # Determine anchor and position based on location
    if loc == "right":
        bbox_to_anchor = (1.0 + pad, 0.0, 1.0, 1.0)
        width, height = size, "100%"
    elif loc == "left":
        bbox_to_anchor = (-(float(size.strip("%")) / 100.0 + pad), 0.0, 1.0, 1.0)
        width, height = size, "100%"
    elif loc == "top":
        bbox_to_anchor = (0.0, 1.0 + pad, 1.0, 1.0)
        width, height = "100%", size
    else:  # bottom
        bbox_to_anchor = (0.0, -(float(size.strip("%")) / 100.0 + pad), 1.0, 1.0)
        width, height = "100%", size

    cax = inset_axes(
        ax,
        width=width,
        height=height,
        loc="lower left",
        bbox_to_anchor=bbox_to_anchor,
        bbox_transform=ax.transAxes,
        borderpad=0,
    )

    cb = self.fig.colorbar(mappable, cax=cax, orientation=orientation, **kwargs)

    if label:
        cb.set_label(label)

    return cb

Adds a logo to the plot.

Parameters

logo : str or array-like, optional Path to the logo image, a URL, or a numpy array. If None, the default MONET logo is used. ax : matplotlib.axes.Axes, optional The axes to add the logo to. Defaults to self.ax. loc : str, optional Location of the logo ('upper right', 'upper left', 'lower right', 'lower left', 'center'). Defaults to "upper right". scale : float, optional Scaling factor for the logo, by default 0.1. pad : float, optional Padding from the edge of the axes, by default 0.05. **kwargs : Any Additional keyword arguments passed to AnnotationBbox.

Returns

matplotlib.offsetbox.AnnotationBbox The added logo object.

Source code in src/monet_plots/plots/base.py
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
def add_logo(
    self,
    logo: str | Any | None = None,
    *,
    ax: matplotlib.axes.Axes | None = None,
    loc: str = "upper right",
    scale: float = 0.1,
    pad: float = 0.05,
    **kwargs: Any,
) -> Any:
    """Adds a logo to the plot.

    Parameters
    ----------
    logo : str or array-like, optional
        Path to the logo image, a URL, or a numpy array.
        If None, the default MONET logo is used.
    ax : matplotlib.axes.Axes, optional
        The axes to add the logo to. Defaults to `self.ax`.
    loc : str, optional
        Location of the logo ('upper right', 'upper left', 'lower right',
        'lower left', 'center'). Defaults to "upper right".
    scale : float, optional
        Scaling factor for the logo, by default 0.1.
    pad : float, optional
        Padding from the edge of the axes, by default 0.05.
    **kwargs : Any
        Additional keyword arguments passed to `AnnotationBbox`.

    Returns
    -------
    matplotlib.offsetbox.AnnotationBbox
        The added logo object.
    """
    import matplotlib.image as mpimg
    from matplotlib.offsetbox import AnnotationBbox, OffsetImage

    from ..plot_utils import get_logo_path

    if ax is None:
        ax = self.ax

    if logo is None:
        logo = get_logo_path()

    if isinstance(logo, str):
        if logo.startswith("http"):
            import io
            import urllib.request

            with urllib.request.urlopen(logo) as url:
                f = io.BytesIO(url.read())
            img = mpimg.imread(f)
        else:
            img = mpimg.imread(logo)
    else:
        img = logo

    imagebox = OffsetImage(img, zoom=scale)
    imagebox.image.axes = ax

    # Mapping of location strings to axes fraction coordinates and box alignment
    loc_map = {
        "upper right": ((1 - pad, 1 - pad), (1, 1)),
        "upper left": ((pad, 1 - pad), (0, 1)),
        "lower right": ((1 - pad, pad), (1, 0)),
        "lower left": ((pad, pad), (0, 0)),
        "center": ((0.5, 0.5), (0.5, 0.5)),
    }

    if loc in loc_map:
        xy, box_alignment = loc_map[loc]
    else:
        # If loc is not a string in loc_map, assume it might be a coordinate
        # tuple, but for simplicity we default to upper right if it's invalid
        if isinstance(loc, tuple) and len(loc) == 2:
            xy = loc
            box_alignment = (0.5, 0.5)
        else:
            xy, box_alignment = loc_map["upper right"]

    ab = AnnotationBbox(
        imagebox,
        xy,
        xycoords="axes fraction",
        box_alignment=box_alignment,
        pad=0,
        frameon=False,
        **kwargs,
    )

    ax.add_artist(ab)
    return ab

close()

Closes the plot figure.

Source code in src/monet_plots/plots/base.py
62
63
64
def close(self):
    """Closes the plot figure."""
    plt.close(self.fig)

save(filename, **kwargs)

Saves the plot to a file.

Parameters:

Name Type Description Default
filename str

The name of the file to save the plot to.

required
**kwargs

Additional keyword arguments for savefig.

{}
Source code in src/monet_plots/plots/base.py
53
54
55
56
57
58
59
60
def save(self, filename, **kwargs):
    """Saves the plot to a file.

    Args:
        filename (str): The name of the file to save the plot to.
        **kwargs: Additional keyword arguments for `savefig`.
    """
    self.fig.savefig(filename, **kwargs)