From 3a10b776c742719e58bc9eb4a15c28f13ec160c0 Mon Sep 17 00:00:00 2001 From: Jon Thielen Date: Sat, 13 Feb 2021 12:40:47 -0600 Subject: [PATCH] Add option for creating inset SkewTs [UNTESTED] --- src/metpy/_vendor/matplotlib.py | 77 +++++++++++++++++++++++++++++++ src/metpy/plots/skewt.py | 81 +++++++++++++++++++++++++++++++-- 2 files changed, 155 insertions(+), 3 deletions(-) create mode 100644 src/metpy/_vendor/matplotlib.py diff --git a/src/metpy/_vendor/matplotlib.py b/src/metpy/_vendor/matplotlib.py new file mode 100644 index 00000000000..da13a551bb6 --- /dev/null +++ b/src/metpy/_vendor/matplotlib.py @@ -0,0 +1,77 @@ +# Copyright (c) 2019 MetPy Developers. +# Distributed under the terms of the BSD 3-Clause License. +# SPDX-License-Identifier: BSD-3-Clause +"""Vendor core functionality used from matplotlib. + +This code has been reproduced from matplotlib 3.3.4 in accord with its license agreement +(reproduced below). + + 1. This LICENSE AGREEMENT is between the Matplotlib Development Team ("MDT"), and the + Individual or Organization ("Licensee") accessing and otherwise using matplotlib software + in source or binary form and its associated documentation. + + 2. Subject to the terms and conditions of this License Agreement, MDT hereby grants + Licensee a nonexclusive, royalty-free, world-wide license to reproduce, analyze, test, + perform and/or display publicly, prepare derivative works, distribute, and otherwise use + matplotlib 3.3.4 alone or in any derivative version, provided, however, that MDT's License + Agreement and MDT's notice of copyright, i.e., "Copyright (c) 2012-2013 Matplotlib + Development Team; All Rights Reserved" are retained in matplotlib 3.3.4 alone or in any + derivative version prepared by Licensee. + + 3. In the event Licensee prepares a derivative work that is based on or incorporates + matplotlib 3.3.4 or any part thereof, and wants to make the derivative work available to + others as provided herein, then Licensee hereby agrees to include in any such work a brief + summary of the changes made to matplotlib 3.3.4. + + 4. MDT is making matplotlib 3.3.4 available to Licensee on an "AS IS" basis. MDT MAKES NO + REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, + MDT MAKES NO AND DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS + FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF MATPLOTLIB 3.3.4 WILL NOT INFRINGE ANY THIRD + PARTY RIGHTS. + + 5. MDT SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF MATPLOTLIB 3.3.4 FOR ANY + INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS A RESULT OF MODIFYING, + DISTRIBUTING, OR OTHERWISE USING MATPLOTLIB 3.3.4, OR ANY DERIVATIVE THEREOF, EVEN IF + ADVISED OF THE POSSIBILITY THEREOF. + + 6. This License Agreement will automatically terminate upon a material breach of its terms + and conditions. + + 7. Nothing in this License Agreement shall be deemed to create any relationship of agency, + partnership, or joint venture between MDT and Licensee. This License Agreement does not + grant permission to use MDT trademarks or trade name in a trademark sense to endorse or + promote products or services of Licensee, or any third party. + + 8. By copying, installing or otherwise using matplotlib 3.3.4, Licensee agrees to be bound + by the terms and conditions of this License Agreement. +""" +import matplotlib.transforms as mtransforms + + +class _TransformedBoundsLocator: + """ + Copyright (c) 2012-2013 Matplotlib Development Team; All Rights Reserved + + This class is reproduced exactly from matplotlib/axes/_base.py, excluding the + modifications made to this comment. + + Axes locator for `.Axes.inset_axes` and similarly positioned axes. + The locator is a callable object used in `.Axes.set_aspect` to compute the + axes location depending on the renderer. + """ + + def __init__(self, bounds, transform): + """ + *bounds* (a ``[l, b, w, h]`` rectangle) and *transform* together + specify the position of the inset axes. + """ + self._bounds = bounds + self._transform = transform + + def __call__(self, ax, renderer): + # Subtracting transSubfigure will typically rely on inverted(), + # freezing the transform; thus, this needs to be delayed until draw + # time as transSubfigure may otherwise change after this is evaluated. + return mtransforms.TransformedBbox( + mtransforms.Bbox.from_bounds(*self._bounds), + self._transform - ax.figure.transSubfigure) diff --git a/src/metpy/plots/skewt.py b/src/metpy/plots/skewt.py index d08fbacb45a..f5da4dee15b 100644 --- a/src/metpy/plots/skewt.py +++ b/src/metpy/plots/skewt.py @@ -23,6 +23,7 @@ import numpy as np from ._util import colored_line +from .._vendor.matplotlib import _TransformedBoundsLocator from ..calc import dewpoint, dry_lapse, el, lcl, moist_lapse, vapor_pressure from ..calc.tools import _delete_masked_points from ..interpolate import interpolate_1d @@ -264,7 +265,7 @@ class SkewT: """ - def __init__(self, fig=None, rotation=30, subplot=None, rect=None, aspect=80.5): + def __init__(self, fig=None, rotation=30, subplot=None, rect=None, aspect=80.5, **kwargs): r"""Create SkewT - logP plots. Parameters @@ -289,6 +290,8 @@ def __init__(self, fig=None, rotation=30, subplot=None, rect=None, aspect=80.5): Aspect ratio (i.e. ratio of y-scale to x-scale) to maintain in the plot. Defaults to 80.5. Passing the string ``'auto'`` tells matplotlib to handle the aspect ratio automatically (this is not recommended for SkewT). + kwargs + Additional keyword arguments passed when creating the axes. """ if fig is None: @@ -301,7 +304,7 @@ def __init__(self, fig=None, rotation=30, subplot=None, rect=None, aspect=80.5): raise ValueError("Specify only one of `rect' and `subplot', but not both") elif rect: - self.ax = fig.add_axes(rect, projection='skewx', rotation=rotation) + self.ax = fig.add_axes(rect, projection='skewx', rotation=rotation, **kwargs) else: if subplot is not None: @@ -313,7 +316,12 @@ def __init__(self, fig=None, rotation=30, subplot=None, rect=None, aspect=80.5): else: subplot = (1, 1, 1) - self.ax = fig.add_subplot(*subplot, projection='skewx', rotation=rotation) + self.ax = fig.add_subplot( + *subplot, + projection='skewx', + rotation=rotation, + **kwargs + ) # Set the yaxis as inverted with log scaling self.ax.set_yscale('log') @@ -342,6 +350,73 @@ def __init__(self, fig=None, rotation=30, subplot=None, rect=None, aspect=80.5): if matplotlib.__version__[:3] > '3.1': self.ax.set_aspect(aspect, adjustable='box') + @classmethod + def inset(cls, ax, bounds, transform=None, zorder=5, rotation=30, aspect=80.5, **kwargs): + r"""Create SkewT - logP plot as an inset. + + Parameters + ---------- + ax : matplotlib.axes.Axes + Source axes on which to place the inset SkewT. + bounds : tuple[float, float, float, float] + Rectangle (left, bottom, width, height) in which to place the axes. This + allows the user to place the axes at an arbitrary point on the figure. See the + ``transform`` argument for controlling the coordinate system used in these bounds. + transform : matplotlib.transforms.Transform, optional + Defaults to ``ax.transData``, the coordinate system for the data. Other options + include ``ax.transAxes`` for axes-relative coordinates, ``fig.transFirgure`` for + figure-relative coordinates, or + ``ax.get_x_axis_transform()``/``ax.get_y_axis_transform()`` for blended + coordinates (data coordinates on one axis and axes coordinates on the other). + zorder : number, optional + Defaults to 5. Adjust higher or lower to change whether it is above or below data + plotted on the parent axes. + rotation : float or int, optional + Controls the rotation of temperature relative to horizontal. Given + in degrees counterclockwise from x-axis. Defaults to 30 degrees. + aspect : float, int, or 'auto', optional + Aspect ratio (i.e. ratio of y-scale to x-scale) to maintain in the plot. + Defaults to 80.5. Passing the string ``'auto'`` tells matplotlib to handle + the aspect ratio automatically (this is not recommended for SkewT). + kwargs + Additional keyword arguments passed when creating the axes. + + Returns + ------- + SkewT + + """ + if transform is None: + transform = ax.transData + + # This segement copied with modification from matplotlib: Copyright (c) 2012-2013 + # Matplotlib Development Team; All Rights Reserved. See license agreement in + # _vendor/matplotlib.py. Modified from original to have parent Axes be an argument + # rather than self, create a SkewT object rather than Axes directly, and use Axes from + # that SkewT instance instead of directly created Axes. + + # This puts the rectangle into figure-relative coordinates. + inset_locator = _TransformedBoundsLocator(bounds, transform) + bounds = inset_locator(ax, None).bounds + + # Create the skewT using the transformed bounds + skew_t = cls( + ax.figure, + rotation=rotation, + rect=bounds, + aspect=aspect, + zorder=zorder, + **kwargs + ) + + # this locator lets the axes move if in data coordinates. + # it gets called in `ax.apply_aspect() (of all places) + skew_t.ax.set_axes_locator(inset_locator) + + # End copy + + return skew_t + def plot(self, pressure, t, *args, **kwargs): r"""Plot data.